这是一本书中的示例代码:
#include <exception>
struct empty_stack: std::exception
{
const char* what() const throw();
};
template<typename T>
class threadsafe_stack
{
private:
std::stack<T> data;
mutable std::mutex m;
public:
threadsafe_stack(){}
threadsafe_stack(const threadsafe_stack& other)
{
std::lock_guard<std::mutex> lock(other.m);
data=other.data;
}
threadsafe_stack& operator=(const threadsafe_stack&) = delete;
void push(T new_value)
{
std::lock_guard<std::mutex> lock(m);
data.push(std::move(new_value));
}
std::shared_ptr<T> pop()
{
std::lock_guard<std::mutex> lock(m);
if(data.empty()) throw empty_stack();
std::shared_ptr<T> const res(std::make_shared<T>(std::move(data.top())));
data.pop();
return res;
}
void pop(T& value)
{
std::lock_guard<std::mutex> lock(m);
if(data.empty()) throw empty_stack();
value=std::move(data.top());
data.pop();
}
bool empty() const
{
std::lock_guard<std::mutex> lock(m);
return data.empty();
}
};
它说:
在empty()和其中任何一个之间存在竞争条件的可能性 pop()函数,但因为代码显式检查 在pop()这场比赛中持有锁时,包含的堆栈是空的 条件不成问题
如果在“全局”互斥锁被锁定的情况下完成它们,这怎么会成为竞争条件?
答案 0 :(得分:3)
我不喜欢你提供的代码。这看起来很误导我。
首先,并发数据结构没有&#34;大小&#34;或者&#34;空虚&#34;。没有可观察到的财产,你可以采取行动。始终返回值true的函数与&#34; empty&#34;一样有用。功能
对于并发数据结构,唯一明智的做法是尝试从中获取数据。那么你要么成功要么失败,你要处理它。您的容器是空的并非特殊情况。编写代码的方式,用户无法保证没有异常!这不对。
最后,共享指针似乎毫无意义。您的容器以其设计方式拥有独特的消费者,因此界面应该就是这样。如果用户希望创建与您无关的共享指针,则由用户决定。
所以,总而言之,我推荐以下界面:
void push(T const & x)
{
std::lock_guard<std::mutex> lock(m);
data.push(x);
}
void push(T && x)
{
std::lock_guard<std::mutex> lock(m);
data.push(std::move(x));
}
bool pop(std::unique_ptr<T> & dst)
{
std::lock_guard<std::mutex> lock(m);
if (data.empty()) { return false; }
dst.reset(::new T(std::move(data.top()));
data.pop();
return true;
}
那就是它,没有别的东西应该是公共接口的一部分(除了emplace
之外 - 就像推送功能一样)。 pop
功能的详细信息由您决定;唯一指针允许您具有非默认可构造的值。您还可以使用dst
的值作为信号,表明元素是否已成功提取并取消bool
。
答案 1 :(得分:3)
竞争条件在客户代码中:
threadsafe_stack<int> st;
...
if (!st.empty()) {
int value = st.pop(); // Kaboom
//...
}
或者换句话说,另一个线程可能在empty()和pop()方法调用之间弹出堆栈。这通常是为什么细粒度锁定不起作用并且需要由客户端程序员来处理。最不可能做到正确的人。
你应该用trypop()替换pop()以使其线程安全。