【发布时间】:2021-02-24 02:49:02
【问题描述】:
C++ Concurrency in Action的代码清单 7.3 中的以下代码:
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));
auto res = old_head ? old_head->data : std::shared_ptr<T>();
if (old_head)
delete old_head;
return res;
}
};
唯一的修改是我在检索数据后立即删除了old_head。作者 Anthony Williams 在第 7.2.2 小节(第 214 页)中说,当多个线程同时调用 pop 时,此时删除 old_head 是不安全的。我想知道为什么会这样。
在我看来,while 循环while(old_head && !head.compare_exchange_weak(old_head, old_head->next)); 确保不会有两个线程在while 循环之后old_head 指向同一个节点。换句话说,如果线程 A 和线程 B 都完成了 while 循环,则线程 A 中的 old_head 必须指向与线程 B 中的 old_head 所指向的节点不同的节点。如果线程 A 和线程 B 中的 old_heads 都指向同一个节点,则至少其中一个仍位于 while 循环之前的点。
假设线程 B 首先完成了 while 循环,而线程 A 仍然在 while 循环之前。假设线程 B 在线程 A 继续之前删除了节点。问题变成了这是否会导致线程 A 的未定义行为。在 while 循环的第一次迭代中,由于线程 B,线程 A 中的old_head 被删除,head 指向与old_head 不同的位置。因此,head.compare_exchange_weak(old_head, old_head->next) 会将head 加载到old_head 并返回false。因为old_next->next现在还没有使用,所以不会有任何问题。我使用以下代码验证这一点
struct Node {
int v;
Node* next;
};
int main() {
std::atomic head = new Node();
auto old_node = head.load();
old_node->next = new Node();
head.exchange(old_node->next);
delete old_node;
std::cout << bool(old_node) << '\n'; // print 1
head.compare_exchange_weak(old_node, old_node->next);
std::cout << old_node->v << '\n'; // print 0
}
在我看来,在检索到数据后立即删除old_head没有问题,因为即使另一个线程中有一个指针指向已删除的内存,只要我们不取消引用它仍然是安全的那个指针。我是对的,还是我误解了什么?
【问题讨论】:
-
你真的只需要两个动作。推,弹出。
-
代码不会像写的那样工作——当 compare_exchange_weak 失败时,它需要在 while 循环中再次调用
head.load()。 -
嗨@ChrisDodd。为什么需要在while循环中调用
head.load()?当compare_exchange_weak失败时,不应该自动将old_head分配给head.load()吗?
标签: c++ multithreading