写的很清晰了 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();
}
};