C++并发编程下栈和队列的线程安全代码 及 注释

写的很清晰了 stack和queue直接看对应的注释应该能看懂 

stack的线程安全代码

削减的接口有一部分为了解决接口冲突

消失的拷贝赋值函数是为了解决数据冲突

#include <exception>
#include <memory>
#include <mutex>
#include <stack>
//相较于标准stack,削减了很多接口
//empty size 在多线程的环境下并不可靠
//因为返回后,其他的线程可以自由的访问栈,push pop 之前的empty和size就有问题
//如果是单线程的情况下,先判断empty再访问top是可行的
//但是 在共享的栈对象里,这样调用会出问题 因为在empty和top这段时间里 随时都会有pop进来
//这就是著名的条件竞争了
//so solution?
//问题来自于接口设计,所以解决也得在接口设计上解决

//当发现栈已经是空的了,那就抛出异常   <-stupid  因为这样如果返回值empty是false,你也需要异常捕获,会让empty多余
//再分析一下 还有什么潜在的条件竞争条件?
//调用top和pop之间,当两个线程运行前面代码,引用同一个栈对象s,那么很容易就会出现pop时间不一致的情况,从而导致value是旧值
//数据库的事务里面的话,这个叫脏读吧可能,读到了本应该已经消失的数据
//所以接口设计就要出现大的改动了
//使用同一个互斥量来保护 top和pop
//question? 如果一个对象的拷贝函数在栈中抛出一个异常,这样的处理方式会出问题
//想想vector,在高负荷的情况下进行拷贝,就会导致内存分配失败。所以会抛出一个异常
//pop的时候会出什么问题呢? 如果这个值返回调用函数的时候,栈才会改变,但拷贝数据的时候,调用函数抛出异常会怎么样?事情真的发生了,
//弹出的数据会丢失,但拷贝失败
//所以 这就是为什么std::stack里,设计人员把这个操作分为两部分
//先获得顶部 的数据,top  再pop 这样的话,不能安全的将元素拷贝,数据仍旧存在 <-我觉得这个也算是STL的强异常安全性的体现
//但是,我们这里为什么不能分开呢? 因为 分开了,就会造成条件竞争
//我们只能把他们放在一起,那么如何解决这些现象呢?
//传入一个引用  将变量的引用作为参数,传入pop函数获得想要的弹出值
// pop(res); 这样的话
//需要在pop里构造栈类型的实例,用来接收目标值  对于一些类型,这样做不现实,临时构造一个实例,从时间资源不划算  甚至有的自定义的类型没有移动拷贝构造
//为什么要有拷贝构造?  返回一个值的时候,有移动就移动,没移动就只能拷贝了

//无异常抛出的拷贝构造和移动构造  忽视了那些没有移动构造的类型,不能接受
//返回指向弹出值的指针
//很好的避免了资源不够抛出异常的问题,但指针需要对内存分配管理,所以最好使用shared_ptr 避免内存泄漏,也能交给标准库控制内存分配,不需要new delete



//综上,解释这里的设计
//栈为什么不能直接赋值呢?
//因为拷贝赋值的情况下,会出现多线程中 多个线程对同一个对象修改,从而影响了线程的安全性
//为了保证线程安全,通常禁止声明拷贝赋值函数
//移动构造和移动赋值一般不会有影响
//所以就剩了push pop empty 三个函数
struct empty_stack : std::exception
{
    const char *what() const throw()
    {
        return "empty stack!";
    };
};
template <typename T>
class threadsafe_stack
{
private:
    std::stack<T> data;
    mutable std::mutex m;

public:
    threadsafe_stack()
        : data(std::stack<T>()) {}
    threadsafe_stack(const threadsafe_stack &other)
    {
        std::lock_guard<std::mutex> lock(other.m);
        data = other.data; // 1 在构造函数体中的执行拷贝
    }
    threadsafe_stack &operator=(const threadsafe_stack &) = delete;
    //加锁
    void push(T new_value)
    {
        std::lock_guard<std::mutex> lock(m);
        data.push(new_value);
    }
    //返回值了 加锁 返回指针 shared_ptr 作为保护
    std::shared_ptr<T> pop()
    {
        std::lock_guard<std::mutex> lock(m);
        if (data.empty())
            throw empty_stack(); // 在调用pop前,检查栈是否为空
        std::shared_ptr<T> const res(std::make_shared<T>(data.top())); // 在修改堆栈前,分配出返回值
        data.pop();
        return res;
    }
    //这个传入引用,会进行一个栈类型的复制,得确保T有移动赋值函数/拷贝赋值函数
    void pop(T &value)
    {
        std::lock_guard<std::mutex> lock(m);
        if (data.empty())
            throw empty_stack();
        value = data.top();
        data.pop();
    }
    //加错 查empty
    bool empty() const
    {
        std::lock_guard<std::mutex> lock(m);
        return data.empty();
    }
};

queue的线程安全实现方法 

#include <queue>
#include <memory>
#include <mutex>
#include <condition_variable>
//queue的队列 
//对整个队列的状态查询 empty size
//查队列的元素 front back
//修改队列的操作 push pop emplace
//固有接口也会有条件竞争   所以 front和pop 合在一起使用 
//队列里传递数据 接收线程需要等待数据的压入  pop的两个变种 try_pop 和 wait and pop 
//try pop 尝试从队列里弹出数据 总会直接返回,无论成功失败
//wait pop 等到有值的时候才会返回

//那么 队列的实例里 包含互斥量和条件变量 
//独立的变量 data_queue 就不需要了
//调用push也不需要同步,
//wait的话 还需要兼顾条件变量的等待
template <typename T>
class threadsafe_queue
{
private:
    mutable std::mutex mut; // 1 互斥量必须是可变的
    std::queue<T> data_queue;
    std::condition_variable data_cond;

public:
    threadsafe_queue()
    {
    }
    threadsafe_queue(threadsafe_queue const &other)
    {
        std::lock_guard<std::mutex> lk(other.mut);
        data_queue = other.data_queue;
    }
    void push(T new_value)
    {
        std::lock_guard<std::mutex> lk(mut);
        data_queue.push(new_value);
        data_cond.notify_one();
    }
    //等到了 这个mut  
    void wait_and_pop(T &value)
    {
        std::unique_lock<std::mutex> lk(mut);
        data_cond.wait(lk, [this]
                       { return !data_queue.empty(); });
        value = data_queue.front();
        data_queue.pop();
    }
    //等待这个mut
    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(std::make_shared<T>(data_queue.front()));
        data_queue.pop();
        return res;
    }
    //这个直接pop
    bool try_pop(T &value)
    {
        std::lock_guard<std::mutex> lk(mut);
        if (data_queue.empty())
            return false;
        value = data_queue.front();
        data_queue.pop();
        return true;
    }
    //无需wait,没有就返回个空的玩意
    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(std::make_shared<T>(data_queue.front()));
        data_queue.pop();
        return res;
    }
    //成员函数 const 传入拷贝构造函数的other形参是const引用      
    //因为其他线程可能有这个类型的非const引用对象 并调用变种成员函数 所以这里需要对互斥量上锁
    //锁住的互斥量是一个可变的操作,所以互斥量需要时mutable 才能在empty和拷贝构造里锁住
    bool empty() const
    {
        std::lock_guard<std::mutex> lk(mut);
        return data_queue.empty();
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值