了解compare_exchange_weak

tte*_*ple 2 c++ multithreading stdatomic

下面的代码是来自https://www.cplusplus.com/reference/atomic/atomic/compare_exchange_weak/的compare_exchange_weak的示例

我不明白 Compare_exchange_weak while 循环内可以接受哪些类型的操作。

在示例中,只要compare_exchange 返回 false,他们就会将 newNode->next 值设置为 oldHead 指针。我不明白这怎么总是有效的。

如果另一个线程处于同一个循环中,并且它在我们设置 oldHead 指针的时间和在我们的线程上compare_exchange 成功的时间之间成功更改了 oldHead 指针,会发生什么情况?那么我们的 newNode 中就会有错误的头指针。我不明白为什么这是不可能的。

例如,如果我在设置 ->next 值后放置 sleep(5),或者在循环中进行一些长计算,这会起作用吗?

标记为“在这里可以安全做什么”的循环是我不明白的部分。

// atomic::compare_exchange_weak example:
#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread
#include <vector>         // std::vector

// a simple global linked list:
struct Node { int value; Node* next; };
std::atomic<Node*> list_head (nullptr);

void append (int val) {     // append an element to the list
  Node* oldHead = list_head;
  Node* newNode = new Node {val,oldHead};

  // what follows is equivalent to: list_head = newNode, but in a thread-safe way:
  while (!list_head.compare_exchange_weak(oldHead,newNode))

    newNode->next = oldHead;                 // <-- WHAT IS SAFE TO DO HERE?

    // WHAT IF I DO SOMETHING STUPID LIKE THIS?
    //sleep(5);

    // OR LIKE THIS:
    //some_long_calculation();    

  }

int main ()
{
  // spawn 10 threads to fill the linked list:
  std::vector<std::thread> threads;
  for (int i=0; i<10; ++i) threads.push_back(std::thread(append,i));
  for (auto& th : threads) th.join();

  // print contents:
  for (Node* it = list_head; it!=nullptr; it=it->next)
    std::cout << ' ' << it->value;
  std::cout << '\n';

  // cleanup:
  Node* it; while (it=list_head) {list_head=it->next; delete it;}

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

mpo*_*ter 6

基本上你可以在循环内做任何你喜欢的事情。compare_exchange(弱和强)只有当且仅当要更新的变量的值等于第一个提供的参数时才会成功。在您的示例中,list_head.compare_exchange_weak(oldHead,newNode)只能成功 iff list_head == oldHead。因此,如果在循环内您有一个睡眠或一些长时间计算,那么很可能其他线程更快并更新list_head,因此您的compare_exchange操作可能会失败。需要注意的是,第一个参数是通过引用传递的。如果比较失败,则使用新值更新给定参数。

\n

存储是完全安全的,因为如果后续失败,则更新,新值将再次存储,然后我们再次尝试(一次又一次......),直到oldHead最终成功。在这种情况下,可以保证存储的值与我们刚刚替换的值相匹配- voil\xc3\xa0,我们已成功将该节点插入到列表中。newNode->nextcompare_exchangeoldHeadnewNode->nextcompare_exchangenewNode->nextlist_head

\n
  // what follows is equivalent to:\n  //   newNode->next = list_head;\n  //   list_head = newNode;\n  // but in a thread-safe way:\n  while (!list_head.compare_exchange_weak(oldHead,newNode)) {\n    // if we are here, the compare_exchange_weak has failed, but\n    // oldHead has been updated with the current value from list_head\n    newNode->next = oldHead;\n  }\n  // if we are here, the compare_exchange_weak was successful, i.e., the value\n  // in list_head (oldHead) did not change between our assignment to newNode->next\n  // and the subsequent (successful) compare_exchange.\n
Run Code Online (Sandbox Code Playgroud)\n
\n

弱版本和强版本的区别在于compare_exchange_weak可能会出现虚假故障。也就是说,在比较结果为真的情况下,它甚至可能失败。之所以有弱版本,是因为在使用 LL/SC 实现比较交换的架构上,acompare_exchange_weak比强版本便宜。一般来说,如果您有像示例中那样的重试循环,您应该更喜欢compare_exchange_weak,因为如果您确实遇到(罕见的)虚假故障,我们只需执行另一次重试。

\n