Thread-safe queues based on locks and condition variables

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》

Guess you like

Origin blog.csdn.net/KPer_Yang/article/details/130174989