基于锁和条件变量的线程安全队列

学习锁和条件变量的线程安全队列,发现一种简单粗暴解决“ 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》

猜你喜欢

转载自blog.csdn.net/KPer_Yang/article/details/130174989