num+在“int num”中可以是原子的吗?

num+在“int num”中可以是原子的吗?

一般情况下,int numnum++(或++num),作为读-修改-写入操作,是非原子..但是我经常看到编译器,例如GCC,为它生成以下代码(试试这里):


因为第5行对应于num++是一条指令,我们能得出结论吗num++ 是原子的在这种情况下?

如果是这样的话,这是否意味着num++可以在并发(多线程)方案中使用,而不存在任何数据竞争的危险。(例如,我们不需要去做,std::atomic<int>加上相关的成本,因为它是原子的)?

更新

注意这个问题是是否增量原子(它不是,也是这个问题的开场白)。关键是它是否能,会,可以在特定情况下,即是否可以在某些情况下利用单指令性质来避免lock前缀。而且,正如公认的答案在关于单处理器计算机的一节中提到的,以及这个答案在评论和其他人的解释中,它可以(虽然没有使用C或C+)。


达令说
浏览 720回答 3
3回答

一只斗牛犬

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

BIG阳

没有许多复杂的指示,如add DWORD PTR [rbp-4], 1很像CISC的风格。它执行三种操作:从内存加载操作数、增量操作数、将操作数存储回内存。在这些操作期间,CPU获取并释放总线两次,在任何其他代理之间也可以获得总线,这违反了原子性。AGENT&nbsp;1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AGENT&nbsp;2load&nbsp;X&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; inc&nbsp;C &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;load&nbsp;X &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inc&nbsp;C &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;store&nbsp;X store&nbsp;Xx只增加一次。
打开App,查看更多内容
随时随地看视频慕课网APP