我正在通过Anthony Williams Concurrency for C ++ 11。
我对锁定自由堆栈的弹出实现感到有点困惑。
template<typename T>
class lock_free_stack
{
private:
struct node
{
std::shared_ptr<T> data;
node* next;
node(T const& data_): data( std::make_shared<T>(data_)){}
};
std::atomic<node*> head;
public:
void push(T const& data)
{
node* const new_node = new node(data);
new_node->next = head.load();
while(!head.compare_exchange_weak(new_node->next, new_node));
}
std::shared_ptr<T> pop()
{
node* old_head = head.load();
while( old_head && !head.compare_exchange_weak(old_head, old_head->next));
// Does This not return the data of the next node before the head if
// compare_exchange_weak returns true
return old_head ? old_head->data : std::shared_ptr<T>();
}
};
导致我混淆的一行是pop函数的最后两行。
while( old_head && !head.compare_exchange_weak(old_head, old_head->next));
return old_head ? old_head->data : std::shared_ptr<T>();
如果compare_exchange_weak函数返回true,那么它是否会将old_head更改为堆栈中的下一个节点?当old_head不是nullptr时,这将导致return语句从下一个节点返回数据而不是堆栈顶部的旧头。
我是否错误地解释了这个?
答案 0 :(得分:3)
除了我的评论,如果compare_exchange_weak成功,它设法将head
从值old_head
更新为值old_head->next
,因此old_head不再是列表的一部分,因此可以正确归还。
仅当它返回false
时才会修改old_head
编辑:显示上述代码的问题。
首先,如果2个主题进入pop
并且都读取head
(通过head.load()
)并获得相同的old_head
线程一被换出(比方说)
线程2继续运行,弹出头并返回调用者,然后调用者删除保存节点的值。
线程1然后恢复并尝试阅读old_head->next
甚至调用 compare_exchange_weak。
但是,old_head指向已被删除的内存。未定义的行为,如果你是段错误的话,你很幸运。
其次,这有经典的ABA问题。我不打算解释这个,因为它有充分的记录和理解。搜索它。
答案 1 :(得分:1)
当head.compare_exchange_weak
返回false
时,它会修改old_head
。
当它返回true
时,它会修改head
并且不会修改old_head
。
请参阅http://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange和/或http://cpp0x.centaur.ath.cx/atomics.types.operations.req.html#p21