3 回答
TA贡献1784条经验 获得超2个赞
.现在让我们启用优化:
f():
rep ret
好吧,让我们给它一个机会:
void f(int& num)
{
num = 0;
num++;
--num;
num += 6;
num -=5;
--num;
}
结果:
f(int&):
mov DWORD PTR [rdi], 0
ret
另一个观察线程(甚至忽略缓存同步延迟)没有机会观察单个更改。
与之相比:
#include <atomic>
void f(std::atomic<int>& num)
{
num = 0;
num++;
--num;
num += 6;
num -=5;
--num;
}
其结果是:
f(std::atomic<int>&):
mov DWORD PTR [rdi], 0
mfence
lock add DWORD PTR [rdi], 1
lock sub DWORD PTR [rdi], 1
lock add DWORD PTR [rdi], 6
lock sub DWORD PTR [rdi], 5
lock sub DWORD PTR [rdi], 1
ret
现在,每项修改都是:
在另一个线程中可以观察到,并且
尊重发生在其他线程中的类似修改。
原子性不只是在指令级,它涉及到从处理器到缓存,到内存和返回的整个管道。
进一步信息
的更新的优化效果std::atomicS.
c+标准具有“似乎”规则,允许编译器重新排序代码,甚至可以重写代码,条件是结果具有完全相同效果(包括副作用),就好像它只是简单地执行了您的代码。
如果规则是保守的,特别涉及原子。
考虑:
void incdec(int& num) {
++num;
--num;
}
由于没有互斥锁、Atomics或任何其他影响线程间排序的构造,我认为编译器可以自由地将此函数重写为NOP,例如:
void incdec(int&) {
// nada
}
这是因为在c+内存模型中,不可能有另一个线程观察增量的结果。当然,如果num曾.volatile(可能会影响硬件行为)。但是在这种情况下,这个函数将是唯一修改这个内存的函数(否则程序是格式错误的)。
然而,这是一场不同的球赛:
void incdec(std::atomic<int>& num) {
++num;
--num;
}
num是原子。对它的改变必观察其他正在观察的线索。更改这些线程本身的值(例如在增量和减少之间将值设置为100)将对num的最终值产生非常深远的影响。
下面是一个演示:
#include <thread>
#include <atomic>
int main()
{
for (int iter = 0 ; iter < 20 ; ++iter)
{
std::atomic<int> num = { 0 };
std::thread t1([&] {
for (int i = 0 ; i < 10000000 ; ++i)
{
++num;
--num;
}
});
std::thread t2([&] {
for (int i = 0 ; i < 10000000 ; ++i)
{
num = 100;
}
});
t2.join();
t1.join();
std::cout << num << std::endl;
}
}
样本输出:
99
99
99
99
99
100
99
99
100
100
100
100
99
99
100
99
99
100
100
99
TA贡献1859条经验 获得超6个赞
add DWORD PTR [rbp-4], 1
AGENT 1 AGENT 2load X inc C load X inc C store X store X
- 3 回答
- 0 关注
- 719 浏览
添加回答
举报