在Anthony Williams的第7.2.1节中标题为“C ++ Concurrency in Action”的书中,列出了一个无锁堆栈实现:
template <typename T>
class lock_free_stack {
struct node {
shared_ptr<T> data_;
node* next_;
node(const T& data) : data_(make_shared(data)) {}
};
atomic<node*> head_;
public:
void push(const T& data)
{
node* new_node = new node(data);
new_node->next_ = head_.load();
while(!head.compare_exchange_weak(new_node->next_, new_node));
}
shared_ptr<T> pop()
{
node* old_head = head_.load();
while (old_head &&
!head_.compare_exchange_weak(old_head, head_->next_));
return old_head ? old_head->data_ : shared_ptr<T>();
}
};
然后在7.2.2节中,作者说“...在pop()中,我们选择泄漏节点以避免竞争条件,其中一个线程删除一个节点,而另一个线程仍然拥有指向它的指针,它只是关于解除引用。“
1)我不明白为什么会出现这种情况以及为什么以下pop()函数会导致竞争条件:
shared_ptr<T> pop()
{
node* old_head = head_.load(); // (1)
while (old_head &&
!head_.compare_exchange_weak(old_head, head_->next_)); // (2)
shared_ptr<T> res; // (3)
if (old_head) {
res.swap(old_head->data);
delete old_head;
return res;
} else {
return {};
}
}
2)为什么对于同时调用pop()的多个线程,'old_head'变量可以指向第(3)行之后的同一个节点对象?
线程1进行到(2)。它开始评估head_->next
。它将head_
加载到寄存器中,然后放弃优先级。
线程2从函数的开始到结束进行。它通过删除它来删除head_
并返回head_
的内容。
线程1醒来。它跟随head_
在一个寄存器获得->next
字段。但是线程2已经删除了head_
指向的数据,我们只是按照悬空指针。
我有同样的困惑阅读,并试图谷歌答案...我找不到答案,最后去检查compare_exchange_weak参考。我们缺少的部分是你传递第二个所需参数的时间,你已经取消引用了悬空指针...你无法真正逃脱,因为函数需要知道你传入的是什么,因此解除引用它。