Learn the thread-safe queue of locks and condition variables, and find a simple and crude way to solve the problem of "exception caused by std::shared_ptr<> construction". It is recommended that friends who study this blog read my code comments.
1. Solve a problem. In the lock-based thread safety stack, the pop operation needs to be judged empty. If a semaphore is used, the notification method does not need to be judged empty. After waiting for the push action, it is enough to notify the pop element;
2. Pay attention to the exception caused by the construction of std::shared_ptr<>, which is also experienced in the stack. The exception will bring unpredictable effects to asynchronous code; the solution here is to avoid constructing during pop, which requires queue The push element is std::shared_ptr<>, which is naturally simple and rude to solve the problem;
#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后,需要唤醒一个线程去处理
}
};
reference:
1、《C+±Concurrency-In-Action-2ed》