作者:狄言洁_171 | 来源:互联网 | 2022-12-07 20:16
通过本书学习,它介绍了如何实现像更复杂的操作operator*
的std::atomic
.实现使用compare_exchange_weak
,我想我明白这是如何工作的.现在,我自己实现了一些东西,看一看.
#include
#include
#include
/*template ::value>>
std::atomic& operator*=(std::atomic& t1, T t2) {
T expected = t1.load();
while(!t1.compare_exchange_weak(expected, expected * t2))
{}
return t1;
}*/
template ::value>>
std::atomic& operator*=(std::atomic& t1, T t2) {
T expected = t1.load();
t1.compare_exchange_weak(expected, expected * t2);
return t1;
}
int main() {
std::atomic t1 = 5;
std::atomic t2;
t2 = (t1 *= 5).load();
std::cout <<"Atomic t1: " <
我有两个版本的代码,书的版本被注释掉了.我不明白为什么我应该等待繁忙的循环来执行原子compare_exchange
.在我的版本中,我只是在它自己的行上调用它并查看Godbolt中生成的程序集,两者都使用
lock cmpxchg dword ptr [rsp + 8], ecx
看起来和我很相似 那么,为什么我需要一个像书中那样的等待循环来使这个东西成为原子?是不是我的版本也很好并且原子地工作?
1> rmm19433..:
想象一下你的调用load
和compare_exchange_weak
另一个线程改变的值.expected
已不再是当前值.
compare_exchange_weak
工作原理如下:
原子地将*this的(对象表示(直到C++ 20)/值表示(自C++ 20))与预期的表示进行比较,如果这些是按位相等的,则将前者替换为所需的(执行读取 - 修改) - 写操作).否则,将存储在*this中的实际值加载到预期值(执行加载操作).
cppreference
基于上面的描述t1
不会改变,你的乘法也不会被存储.通过循环,您可以确保更新t1
并存储乘法的结果或更新,expected
并在循环的下一次迭代中再次尝试(循环仅在第一个案例发生时停止).
编辑:您可以通过模拟并发访问来"尝试"它.在交换结果之前,另一个线程进入并更改原子的值.在下面的compare_exchange_weak
唯一影响expected
.
+----------- Thread 1 -----------+---------- Thread 2 ----------+
| ex = t1.load() | |
| | t1.store(42) |
| t1.cmp_xchg_w(ex, ex * t2) | |
此代码模拟并发访问并让单个线程休眠.
#include
#include
#include
#include
#include
template ::value>>
std::atomic& operator*=(std::atomic& t1, T t2) {
using namespace std::chrono_literals;
T expected = t1.load();
std::this_thread::sleep_for(400ms);
t1.compare_exchange_weak(expected, expected * t2);
return t1;
}
int main() {
std::atomic t1 = 5;
std::atomic t2;
std::thread th1([&](){
t2 = (t1 *= 5).load();
});
std::thread th2([&](){
using namespace std::chrono_literals;
std::this_thread::sleep_for(100ms);
t1.store(8);
});
th1.join();
th2.join();
std::cout <<"Atomic t1: " <