++i 和 i++ 是否为原子操作?

2021年02月22日 周一 天气晴 【不悲叹过去,不荒废现在,不惧怕未来】



1. 操作系统的原子操作

原子操作是不可分割的,在执行完毕前不会被任何其它任务或事件中断:

在单线程中, 能够在单条指令中完成的操作都可以认为是 原子操作,不能在单条指令中完成的操作也都可以认为不是原子操作,因为中断可以且只能发生于指令之间;

在多线程中,不能被其它进程(线程)打断的操作就叫原子操作;

2. 从汇编代码看 ++i 和 i++ 是否为原子操作

++i;
00007FF7D69F17E8  mov         eax,dword ptr [i]  
00007FF7D69F17EB  inc         eax  
00007FF7D69F17ED  mov         dword ptr [i],eax  
i++;
00007FF7D69F17F0  mov         eax,dword ptr [i]  
00007FF7D69F17F3  inc         eax  
00007FF7D69F17F5  mov         dword ptr [i],eax  

可见,无论是 ++i 还是 i++ ,都是使用三个指令来实现的:

1、将值从内存拷贝到寄存器;
2、寄存器自增;
3、将值从寄存器拷贝到内存;

所以,++i 和 i++ 都不是原子操作。

3. 常见问题:进程有一个全局变量i,还有两个线程。i++ 在两个线程里边分别执行100次,能得到的最大值和最小值分别是多少?

3.1 多核CPU最小值为2,最大值200

由上可知,i++ 是由3条指令构成的运算操作,两个线程在 i 变量上共计需要执行100(次循环)*3(条指令)*2(个线程)= 600条指令,这600条指令在某种排列下会导致最终i的值仅为2。

假设两个线程的执行步骤如下,最终 i 的值仅为2:

  1. 线程A执行第一次i++,取出内存中的i,值为0,存放到寄存器后执行加1,此时CPU1的寄存器中值为1,内存中为0;

  2. 线程B执行第一次i++,取出内存中的i,值为0,存放到寄存器后执行加1,此时CPU2的寄存器中值为1,内存中为0;

  3. 线程A继续执行完成第99次i++,并把值放回内存,此时CPU1中寄存器的值为99,内存中为99;

  4. 线程B继续执行第一次i++,将其值放回内存,此时CPU2中的寄存器值为1,内存中为1;

  5. 线程A执行第100次i++,将内存中的值取回CPU1的寄存器,并执行加1,此时CPU1的寄存器中的值为2,内存中为1;

  6. 线程B执行完所有操作,并将其放回内存,此时CPU2的寄存器值为100,内存中为100;

  7. 线程A执行100次操作的最后一部分,将CPU1中的寄存器值放回内存,内存中值为2;

结果为200的情况相对简单,只需要两个线程间隔操作即可。

3.2 单核CPU最小值为100,最大值200

两个线程分别记为线程1和线程2,i++相当于取出i的值,加1,再放回去

第一种极端情况:每次线程一取出i的值后CPU时间切换到线程二,线程二也取出i的值,取到的值和线程一相等,线程二给i加一后放回去,线程一也将i加一后放回去,放回去的值也相等,相当于两个线程都执行一次i++操作,i的值只增加1,这样操作100次i的值为100

第二种极端情况:线程一和线程二间隔操作,即线程一对i++操作完成,把已经加一的数据放回去之后线程二再操作,轮流进行,最后每个线程都对i加了100次,i的值为200


参考文献

i++(++i)不是原子操作

进程有一个全局变量i,还有有两个线程。i++在两个线程里边分别执行100次,能得到的最大值和最小值分别是多少?

猜你喜欢

转载自blog.csdn.net/m0_37433111/article/details/113931514