原子操作(待更新)

转载:https://blog.csdn.net/just_kong/article/details/99289539

1、原子操作
原子操作(atomic operation)指的是由多步操作组成的一个操作。如果该操作不能原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。
现代操作系统中,一般都提供了原子操作来实现一些同步操作,所谓原子操作,也就是一个独立而不可分割的操作。在单核环境中,一般的意义下原子操作中线程不会被切换,线程切换要么在原子操作之前,要么在原子操作完成之后。更广泛的意义下原子操作是指一系列必须整体完成的操作步骤,如果任何一步操作没有完成,那么所有完成的步骤都必须回滚,这样就可以保证要么所有操作步骤都未完成,要么所有操作步骤都被完成。
例如在单核系统里,单个的机器指令可以看成是原子操作(如果有编译器优化、乱序执行等情况除外);在多核系统中,单个的机器指令就不是原子操作,因为多核系统里是多指令流并行运行的,一个核在执行一个指令时,其他核同时执行的指令有可能操作同一块内存区域,从而出现数据竞争现象。多核系统中的原子操作通常使用内存栅障(memory barrier)来实现,即一个CPU核在执行原子操作时,其他CPU核必须停止对内存操作或者不对指定的内存进行操作,这样才能避免数据竞争问题。
在C++11之前,C++标准中并没有对原子操作进行规定。vs和gcc编译器提供了原子操作的api。
2、windows原子操作api
Win32 API中常用的原子操作主要有三类,一种是加1减1操作,一种是比较交换操作,另外一种是赋值(写)操作。
(1)原子加1减1操作
LONG InterlockedIncrement( LONG volatile* Addend);
LONG InterlockedDecrement( LONG volatile* Addend);
(2)  比较并交换操作
LONG InterlockedCompareExchange( LONG volatile*Destination, LONG Exchange, LONG Comperand );
这个操作是先将Comperand的值和Destination指向变量的值进行比较,如果相等就将Exchange变量的值赋给Destination指向的变量。返回值为未修改前的Destination位置的初始值。
(3)原子写操作
LONG InterlockedExchange( LONG volatile* Target, LONG Value);
InterlockedExchange的作用为将Value的值赋给Target指向的变量,返回Target指向变量未被赋值前的值。
3、GCC编译器提供的原子操作API
type __sync_fetch_and_add (type *ptr, type value);
type __sync_fetch_and_sub (type *ptr, type value);
type __sync_fetch_and_or (type *ptr, type value);
type __sync_fetch_and_and (type *ptr, type value);
type __sync_fetch_and_xor (type *ptr, type value);
type __sync_fetch_and_nand (type *ptr, type value);
type __sync_add_and_fetch (type *ptr, type value);
type __sync_sub_and_fetch (type *ptr, type value);
type __sync_or_and_fetch (type *ptr, type value);
type __sync_and_and_fetch (type *ptr, type value);
type __sync_xor_and_fetch (type *ptr, type value);
type __sync_nand_and_fetch (type *ptr, type value);
4、C++11提供的原子操作
C++11中在<atomic>中定义了atomic模板类,atomic的模板参数类型可以为int、long、bool等等,C++中称为trivially copyable type。atomic_int、atomic_long为atomic模板实例化后的宏定义。atomic具体的原子操作函数可以参考http://www.cplusplus.com/reference/atomic/atomic/?kw=atomic。
5、原子操作的效率
(1)不加锁也不使用原子变量的程序
程序代码:
输出:
 
(2)加锁的程序
程序代码:
 
输出:
 
3.使用C++11原子变量的程序
程序代码和输出:
 

 

 (4)结论
上面的第一个不加锁程序肯定是最不推荐使用的,因为它的执行结果都不正确。第二个程序是使用的常规锁来解决问题,结果正确,但是耗时较久。第三个程序使用的是C++11引入的原子数据类型,使用它程序结果正确,在运行速度上也比加锁的版本快很多。所以,在我们平常写程序的过程中,推荐使用C++11引入的原子变量。
6、什么时候使用原子操作
在多线程并发的条件下,所有不是原子性的操作需要保证原子性时,都需要进行原子操作处理。
例:
long count = 0;
void func()
{
  count++;
}
如果有n个线程同时执行这段代码,所有线程执行完后,count的值不一定等于n。因为count++不是一个原子操作,编译成汇编代码,如下所示:
MOV   eax, [count] 
INC  eax
MOV [count], eax
在cpu执行时 
第一步,先将 count所在内存的值加载到寄存器;
第二步,将寄存器的值自增1;
第三步,将寄存器中的值写回内存。
所以当第一个线程将count值加载到寄存器,并完成自增1,这时寄存器中的值为2,如果此时cpu调度将此线程中断,并执行完其它线程后,再将此线程调度执行,此时,会将2写入到count。count最后的值就成了2。如果要确保改结果正确,那么cout++就要使用原子操作类型。
上述示例中,count的操作如果为count = count + 2,那么也需要原子操作,而如果为count=2或者count==2,则不需要原子操作,因为它们本身的操作就是具有原子性的。
————————————————
版权声明:本文为CSDN博主「justkong」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/just_kong/java/article/details/99289539

猜你喜欢

转载自www.cnblogs.com/zhiminyu/p/12577886.html
今日推荐