std::stack容器的接口包括 empty(), size(), top(), push(), pop()等。
问题
其原接口在多线程的情况下,会持续很多问题。
例如,在std::stack容器的接口中,在多线程下应用时,empty()和size()的结果是不可信的。因为尽管在某线程调用empty()或size()时,返回值可能是正确的,但是一旦函数返回,其他线程就不再受限,从而能自由地访问栈容器,可能马上会有新元素入栈,或者现有的元素立即出栈,这会使前面线程得到的结果失效。
其次,top()和pop()之间也存在着竞争条件,例如
线程A | 线程B |
if(!s.empty()); | |
if(!s.empty()); | |
int const value = s.top(); | |
int const value = s.top(); | |
s.pop() | |
do_something(value); | s.pop() |
do_something(value); |
假设,当前只有两个线程正在运行,且在两个top()调用之间不存在其他操作,那么栈容器不会被更改,即两个线程会得到相同的值。另外,在top()与pop()的调用之间也没有其他调用,结果栈顶元素被调用了两次,但栈顶的第二个元素却始终没被读取就丢弃了。
解决方法
为了线程安全,在每次操作之前,都加锁
面对第二个问题,将top()和pop()操作合二为一
#include <memory>
#include <mutex>
#include <stack>
using namespace std;
struct empty_stack :std::exception
{
const char* what() const throw();
};
template<typename T>
class threadsafe_stack
{
private:
stack<T> data;
mutable mutex m;
public:
threadsafe_stack() {};
threadsafe_stack(const threadsafe_stack& other)
{
lock_guard<mutex> lock(other.m);
data = other.data;
}
threadsafe_stack& operator=(const threadsafe_stack&) = delete;
void push(T new_value)
{
lock_guard<mutex> lock(m);
data.push(move(new_value));
}
shared_ptr<T> pop()
{
lock_guard<mutex> lock(m);
if (data.empty()) throw empty_stack();
shared_ptr<T>const res(std::make_shared<T>(data.top()));
data.pop();
return res;
}
void pop(T& value)
{
lock_guard<mutex> lock(m);
if (data.empty()) throw empty_stack();
value = data.top();
data.pop();
}
bool empty() const
{
lock_guard<mutex> lock(m);
return data.empty();
}
};