学习锁和条件变量的线程安全队列,发现一种简单粗暴解决“ std::shared_ptr<>构造带来的异常”问题的方法。建议学习这篇博客的朋友看我的代码注释哈
1、解决一个问题,在基于锁的线程安全栈中,pop操作需要判空,如果用信号量的话,使用通知的方式就不需要判空,等待有push动作后,再通知pop元素就可以;
2、注意由于 std::shared_ptr<>构造带来的异常,这个在stack里面也体会到了,异常会给异步代码带来不可预测的影响;这里的解决方案是避免在pop时构造,就需要queue的push的元素是 std::shared_ptr<>,自然简单粗暴解决问题;
#include <queue>
#include <mutex>
#include <condition_variable>
#include <memory>
template<typename T>
class threadsafe_queue
{
private:
mutable std::mutex mut;//锁
std::queue<std::shared_ptr<T> > data_queue;
std::condition_variable data_cond;//条件变量
public:
threadsafe_queue()
{
}
//等待获取,即使是空的,也要等到非空为止,这时候是阻塞的,不需要占用CPU时间片;
void wait_and_pop(T& value)
{
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(lk,[this]{
return !data_queue.empty();});//使用条件变量,不需要判断队列空;
value=std::move(*data_queue.front());
data_queue.pop();
}
//try语义指的是尝试pop,非空&&获得锁的话就可以pop
bool try_pop(T& value)
{
std::lock_guard<std::mutex> lk(mut);
if(data_queue.empty())
return false;
value=std::move(*data_queue.front());
data_queue.pop();
}
//有个缺陷,如果wait_and_pop抛出异常(例如在shared_ptr<T>构造时),其他线程会不再运行;
//因为如果wait_and_pop等到一个信号量,但是又抛出异常的话,push函数那里就认为信号量没有被接收,不会发出的信号,其他线程就获取不到信号量,无法执行;
//如何改进?将 std::shared_ptr<> 的初始化过程移到push()中,并且存储 std::shared_ptr<> 实例,而不是直接使用数据值,
//将 std::shared_ptr<> 拷贝到内部 std::queue<> 中就不会抛出异常了,这样wait_and_pop()又是安全的了。下面的代码,就是根据第三种方案修改的。
std::shared_ptr<T> wait_and_pop()
{
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(lk,[this]{
return !data_queue.empty();});
std::shared_ptr<T> res=data_queue.front();
data_queue.pop();
return res;
}
std::shared_ptr<T> try_pop()
{
std::lock_guard<std::mutex> lk(mut);
if(data_queue.empty())
return std::shared_ptr<T>();
std::shared_ptr<T> res=data_queue.front();
data_queue.pop();
return res;
}
bool empty() const
{
std::lock_guard<std::mutex> lk(mut);
return data_queue.empty();
}
void push(T new_value)
{
//防止shared_ptr<T>构造异常,简单粗暴的方法就是避免在wait_and_pop()构造shared_ptr!!!,
//直接在queue存储shared_ptr,这个解决问题的方法真的很粗暴很巧妙!!
std::shared_ptr<T> data(
std::make_shared<T>(std::move(new_value)));
std::lock_guard<std::mutex> lk(mut);
data_queue.push(data);
data_cond.notify_one();//push后,需要唤醒一个线程去处理
}
};
参考:
1、《C+±Concurrency-In-Action-2ed》