了解compare_exchange_weak

问题描述

以下代码是来自https://www.cplusplus.com/reference/atomic/atomic/compare_exchange_weak/

的compare_exchange_weak的示例

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

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

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

例如,如果我放置了sleep(5),或者在设置了-> next值后在循环中做了一些长计算,这行得通吗?

标记为“要在这里做什么”的循环是我不了解的部分。

// 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;
}

解决方法

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

oldHead存储在newNode->next是绝对安全的,因为如果随后compare_exchange失败,oldHead被更新,则新值将再次存储在newNode->next中然后我们再试一次又一次,直到compare_exchange最终成功。在这种情况下,可以保证存储在newNode->next中的值与我们刚刚在list_head中替换的值相匹配-voilà,我们已将节点成功插入列表中。

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

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

,

compare_exchange_(weak/strong)设计用于原子数据的常规修改。不能完全确定两者之间的区别是什么-我认为当原子不是无锁且多次无法获得锁的情况下。

当无法锁定数据时,如何执行一般修改?想想看。您计算了原子数据的一些常规功能,但同时对数据进行了修改。不可能避免,因为一个人不能锁定原子。

因此人们想到的是检查从函数启动时起是否更改了数据,如果没有更改,则将其替换为新值。否则,它只是将期望值替换为当前值。因此,通常只需简单地重试即可计算函数,直到两次计算之间的数据不变或不再需要计算为止。

示例:您有一个整数x,并且您想用y更新原子整数max(x,y),而另一个线程可能用不同的x来做同样的事情

因此,从理论上讲,您可以为compare_exchange_(weak/strong)放入任何函数,但是如果计算时间过长而导致重试的价格过高,这将是不切实际的。在这种情况下,请使用互斥锁,因此无需重试。