了解 compare_exchange_weak
Understanding compare_exchange_weak
下面的代码是 compare_exchange_weak 来自 https://www.cplusplus.com/reference/atomic/atomic/compare_exchange_weak/
的示例
我不明白 compare_exchange_weak while 循环中可以接受哪些类型的操作。
在示例中,只要 compare_exchange returns 为假,他们就会将 newNode->next 值设置为 oldHead 指针。我不明白这怎么总是有效的。
如果另一个线程在同一个循环中,并且在我们设置它的时间和 compare_exchange 在我们的线程上成功的时间之间成功更改了 oldHead 指针,会发生什么情况?那么我们的新节点中就会有错误的头指针。我不明白为什么这是不可能的。
例如,如果我在设置 ->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;
}
compare_exchange_(weak/strong)
专为原子数据的一般修改而设计。不完全确定两者之间有什么区别 - 我认为这与原子未锁定且多次未能获得锁定的情况有关。
在无法锁定数据的情况下如何进行通用修改?想一想。您计算原子数据的一些一般功能,但同时修改了数据。无法避免,因为无法锁定原子。
所以人们想到的是检查数据是否从函数启动时发生变化,如果没有,则用新值替换它。否则它只是用当前值替换预期值。所以通常人们只是简单地重试计算函数,直到数据在计算之间没有变化或者计算变得不必要。
示例:您有一个整数 x
并且您想要将原子整数 y
更新为 max(x,y)
而另一个线程可能正在使用 [=11 的不同值进行相同操作=].
所以理论上你可以为compare_exchange_(weak/strong)
放入任何函数,但如果计算时间很长,那么重试的代价太大,就不实用了。在这种情况下使用互斥锁,因此不需要重试。
基本上你可以在循环内做任何你想做的事。 compare_exchange
(弱和强)只有当且仅当要更新的变量的值等于第一个提供的参数时才会成功。在您的示例中,list_head.compare_exchange_weak(oldHead,newNode)
只能在 list_head == oldHead
时成功。因此,如果在循环中你有一个睡眠或一些长时间的计算,那么很有可能其他线程更快并更新 list_head
,所以你的 compare_exchange
操作很可能会失败。重要的是要注意第一个参数是通过引用传递的。如果比较失败,则给定参数更新为新值。
在newNode->next
中存储oldHead
是绝对安全的,因为如果随后的compare_exchange
失败,oldHead
被更新,新值再次存储在[=17中=] 然后我们再试一次(一次又一次,......)直到 compare_exchange
最终成功。在这种情况下,可以保证存储在 newNode->next
中的值与我们刚刚在 list_head
中替换的值匹配 - 瞧,我们已经成功地将节点插入列表中。
// 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 的架构上,compare_exchange_weak
比强版本便宜。一般来说,如果你有一个像你的例子中那样的重试循环,你应该更喜欢 compare_exchange_weak
,因为如果你真的有一个(罕见的)虚假失败,我们只需执行另一次重试。
下面的代码是 compare_exchange_weak 来自 https://www.cplusplus.com/reference/atomic/atomic/compare_exchange_weak/
的示例我不明白 compare_exchange_weak while 循环中可以接受哪些类型的操作。
在示例中,只要 compare_exchange returns 为假,他们就会将 newNode->next 值设置为 oldHead 指针。我不明白这怎么总是有效的。
如果另一个线程在同一个循环中,并且在我们设置它的时间和 compare_exchange 在我们的线程上成功的时间之间成功更改了 oldHead 指针,会发生什么情况?那么我们的新节点中就会有错误的头指针。我不明白为什么这是不可能的。
例如,如果我在设置 ->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;
}
compare_exchange_(weak/strong)
专为原子数据的一般修改而设计。不完全确定两者之间有什么区别 - 我认为这与原子未锁定且多次未能获得锁定的情况有关。
在无法锁定数据的情况下如何进行通用修改?想一想。您计算原子数据的一些一般功能,但同时修改了数据。无法避免,因为无法锁定原子。
所以人们想到的是检查数据是否从函数启动时发生变化,如果没有,则用新值替换它。否则它只是用当前值替换预期值。所以通常人们只是简单地重试计算函数,直到数据在计算之间没有变化或者计算变得不必要。
示例:您有一个整数 x
并且您想要将原子整数 y
更新为 max(x,y)
而另一个线程可能正在使用 [=11 的不同值进行相同操作=].
所以理论上你可以为compare_exchange_(weak/strong)
放入任何函数,但如果计算时间很长,那么重试的代价太大,就不实用了。在这种情况下使用互斥锁,因此不需要重试。
基本上你可以在循环内做任何你想做的事。 compare_exchange
(弱和强)只有当且仅当要更新的变量的值等于第一个提供的参数时才会成功。在您的示例中,list_head.compare_exchange_weak(oldHead,newNode)
只能在 list_head == oldHead
时成功。因此,如果在循环中你有一个睡眠或一些长时间的计算,那么很有可能其他线程更快并更新 list_head
,所以你的 compare_exchange
操作很可能会失败。重要的是要注意第一个参数是通过引用传递的。如果比较失败,则给定参数更新为新值。
在newNode->next
中存储oldHead
是绝对安全的,因为如果随后的compare_exchange
失败,oldHead
被更新,新值再次存储在[=17中=] 然后我们再试一次(一次又一次,......)直到 compare_exchange
最终成功。在这种情况下,可以保证存储在 newNode->next
中的值与我们刚刚在 list_head
中替换的值匹配 - 瞧,我们已经成功地将节点插入列表中。
// 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 的架构上,compare_exchange_weak
比强版本便宜。一般来说,如果你有一个像你的例子中那样的重试循环,你应该更喜欢 compare_exchange_weak
,因为如果你真的有一个(罕见的)虚假失败,我们只需执行另一次重试。