c++ - Different ways of implementing atomic *=
问题描述
Studying through a book, it explains how to implement more complex operations like operator*
for std::atomic<T>
. Implementation uses compare_exchange_weak
and I think I understood how this works. Now, I implemented things myself, take a look.
#include <type_traits>
#include <atomic>
#include <iostream>
/*template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
std::atomic<T>& operator*=(std::atomic<T>& t1, T t2) {
T expected = t1.load();
while(!t1.compare_exchange_weak(expected, expected * t2))
{}
return t1;
}*/
template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
std::atomic<T>& operator*=(std::atomic<T>& t1, T t2) {
T expected = t1.load();
t1.compare_exchange_weak(expected, expected * t2);
return t1;
}
int main() {
std::atomic<int> t1 = 5;
std::atomic<int> t2;
t2 = (t1 *= 5).load();
std::cout << "Atomic t1: " << t1 << "\n";
std::cout << "Atomic t2: " << t2 << "\n";
}
I have two versions of the code, book's version is commented out. I don't get why I should wait on a busy-loop to perform atomic compare_exchange
. In my version, I've just called it on its own line and looking at the generated assembly in Godbolt, both uses
lock cmpxchg dword ptr [rsp + 8], ecx
and looks pretty similar to me. So, why should I need a wait-loop like the one in the book to make this thing atomic? Isn't my version also fine and do work atomically?
解决方案
想象一下在您的调用load
和compare_exchange_weak
值被另一个线程更改之间。expected
不再具有当前值。
compare_exchange_weak
工作方式如下:
原子地比较 *this 的(对象表示(C++20 前)/值表示(C++20 起))与期望的,如果它们按位相等,则将前者替换为期望(执行读取修改-写操作)。否则,将存储在 *this 中的实际值加载到预期中(执行加载操作)。 参考
基于上面的描述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 <type_traits>
#include <atomic>
#include <iostream>
#include <chrono>
#include <thread>
template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
std::atomic<T>& operator*=(std::atomic<T>& 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<int> t1 = 5;
std::atomic<int> 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: " << t1 << "\n";
std::cout << "Atomic t2: " << t2 << "\n";
}
推荐阅读
- python - 如何在 Python 中生成相关随机数?
- database - 是否有将 API 密钥和秘密保存到数据库中的最佳实践?
- java - 如何在androidstudio中居中菜单项
- scala - 如何有效地删除 Scala 中的 var
- sql-server - 我们可以在 SQL SERVER 的非聚集索引的包含列列表中包含 UDT 吗?
- java - 无法在 Eclipse 中完成安装 WebSphere
- r - 根据季节开始和结束计算每像素平均降雨量值
- javascript - javascript if 语句因 ejs 失败
- python - 绘制networkx图时出现意外错误
- javascript - 带有等式的 HTML 弹出窗口