原子操作 CAS

参考:

https://zhuanlan.zhihu.com/p/400817892

https://www.bilibili.com/read/cv10686883/

https://blog.csdn.net/www_dong/article/details/119920236

https://blog.csdn.net/niu91/article/details/116308436

https://blog.csdn.net/CringKong/article/details/79966161

1、原子操作

原子操作,不会被线程调度机制打断的操作。这个操作一旦开始,就一直运行到结束,中间不会有任何上下文切换。

典型的原子操作有(原子操作需要硬件支持)

Load / Store :读写内存

Test and Set:针对bool变量,如果为true则返回true,如果为false,则将变量置为true并返回false。 

Clear:将bool变量设为false。 

Exchange:将指定位置的值设置为传入值,并返回其旧值。 

Compare and Swap:将指定位置的值与期望值比较,如果相等则赋值为新值,如果不等则将期望值设置为自身。返回是否设置成功。 

Fetch And 加减乘除系列:对指定位置的值使用传入参数执行加减乘除,并返回旧值。

2、竞态条件(Race Conditon)

int i = 0;
i++;

mov         eax,dword ptr [i]   // 将i加载到eax寄存器
add         eax,1  // eax中的值加一
mov         dword ptr [i],eax  // 将eax中的值赋值到i的地址 

汇编层面有三步操作:读-修改-写。线程1修改完还未写到内存中去,线程2得到CPU开始执行,修改的会是线程1未写到内存中的值,存在线程安全问题。

4、线程安全

解决线程安全通常有以下方法:

1、使用原子操作。

2、加锁:悲观锁或者乐观锁(无锁)

1、atomic_int num; num++;

在msvc中,C++11 提出的 atomic_int 类型,在 num++ 操作时,底层调用windows提供的原子自增函数_InterlockedIncrement 。CAS提供的API

2、悲观锁

mutex 就是一种悲观锁的使用。假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。

3、乐观锁

假设不会发生并发冲突,每次不加锁而是假设没有冲突而去完成某项操作,只在提交操作时检查是否违反数据完整性。

CAS (Compare And Swap)操作是一条CPU的原子指令,所以不会有线程安全问题。

CAS(addr,old,new)

解释:将addr存放的只与old比较,如果等于old,则将new赋值给addr。

C++ 可以实现如下:

bool compare_and_swap(int* pAddr, int nExpected, int nNew) {
    if (*pAddr == nExpected) {
        *pAddr = nNew;
        return true;
    }
    else 
        return false;
}

不同编译器 底层实现不一样,但算法思想一样。

GCC的CAS,GCC4.1+版本中支持CAS的原子操作。

1)bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval, ...) 
2)type __sync_val_compare_and_swap (type *ptr, type oldval, type newval, ...)

C++11中的CAS,C++11中的STL中的atomic类的函数可以让你跨平台。

template< class T > bool atomic_compare_exchange_weak( std::atomic* obj,T* expected, T desired ); 
template< class T > bool atomic_compare_exchange_weak( volatile std::atomic* obj,T* expected, T desired );

Windows的CAS

InterlockedCompareExchange ( __inoutLONGvolatile *Target,
                                __inLONGExchange,
                                __inLONGComperand);

 CAS 存在的问题: ABA。

解决方法: Double CAS,即可以加上版本号。

像状态寄存器、映射到内存地址上的 I/O 操作、涉及硬件操作的变量需要加volatile,因为对它们的每一次操作都有其意义

而并发时,多线程多任务环境下各任务间共享的标志,其实更应该用原子量和内存序,或者直接加互斥锁,以确保共享区操作的原子性和顺序性。

所以其实volatile和atomic是应用于不同场景的,甚至可以叠加使用。比如:

 volatile std::atomic<int> value;

这个式子表示对value的操作都是原子性的,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值