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

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KPer_Yang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值