C/C++中的volatile并不是用来解决多线竞争问题的,而是用来修饰一些程序不可控因素导致变化的变量,比如访问底层硬件设备的变量,来提醒编译器不要对该变量的访问擅自进行优化。
C++11标准明确指出解决多线程的数据竞争问题应该使用原子操作或互斥锁。
高并发服务器经常用到多线程编程,需要对共享数据进行操作,为了保证数据的正确性,有一种有效的方法就是加锁机制,但是这种方式存在以下一些缺点:
为了解决多线程并行情况下使用锁造成性能损耗的问题,我们引入了CAS机制(Compare and Swap)
CAS包含三个操作数:
如果内存位置V内保存的值与预期原值相匹配,那么处理器会总动将内存位置V处的值更新为新值;否则处理器不做任何操作。
无论哪种情况,它都会返回内存位置原来保存的值。
示例如下,判断内存reg里的值是不是oldval,如果是的话,则对其赋值newval。
int compare_and_swap (int* reg, int oldval, int newval)
{
int old_reg_val = *reg;
if(old_reg_val == oldval)
*reg = newval;
return old_reg_val;
}
这个操作可以变种为返回bool值的形式(返回bool值的好处在于,调用者可以知道有没有更新成功) :
bool compare_and_swap (int* reg, int oldval, int newval)
{
if(*reg == oldval)
{
*reg = newval;
return true;
}
return false;
}
CAS是一种有名的无锁算法。无锁编程,即不适用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步。
CAS总结如下:
所谓ABA问题如下:
现实场景:
- 小明银行卡里有100元,此时小明需要取50元,由于系统阻塞,取操作执行两次;
- 在第一次取操作执行成功后,银行卡里剩下50元,在第二次操作到来之前,小明妈妈往小明卡里转入50元;
- 第二次操作来临时,发现卡里余额100元,扣除50元,此时卡里还剩50元
- 小明血亏~~~
虽然P1以为内存地址中的变量值没有改变,继续执行了,但是这个会引发一些潜在的问题。ABA问题最容易发生在lock free的算法中的CAS,因为CAS判断的是指针的地址。如果这个地址被重用,问题就很大了。
为解决ABA为题,我们可以采用具有原子性的内存引用计数等等办法。
解决ABA问题
方法一:维基百科上给了一个解--使用double-CAS (双保险的CAS)
例如,在32位系统上,我们要检查64位的内容.一次用CAS检查双倍长度的值,前半部是指针,后半部分是一个计数器。只有这两个都一样,才算通过检查。
要给内存V处赋新的值,需要并把计数器累加1。这样一来,ABA发生时,虽然值一样,但是计数器就不一样(但是在32位的系统上,这个计数器会溢出回来又从1开始的,这还是会有ABA的问题)
方法二:我们可以在V对象上加上一个版本号,取V对象的时候连版本号也取出来,当V对象每次被修改的时候都将V的版本号进行改变,那样就可以知道V对象有没有被修改过。
代码最终都会被翻译为CPU指令,一条最简单加减法语句都有可能会被翻译成几条指令执行;
为了避免语句在CPU这一层级上的指令交叉带来的行为不可知,在多线程程序设计时我们必须通过一些方式来进行规范;这里面最常见的做法就是引入互斥锁,但互斥锁是操作系统这一层级的,最终映射到CPU上也是一堆指令,是指令就必然会带来额外的开销;
既然CPU指令是多线程不可再分的最小单元,那我们如果有办法将代码语句和指令对应起来,不就不需要引入互斥锁从而提高性能了吗?而这个对应关系就是所谓的原子操作;
使用原子操作模拟互斥锁的行为就是自旋锁,常用的自旋锁模型有:
TAS, Test-and-set,/* 有且只有atomic_flag类型与之对应 */
CAS, Compare-and-swap,
/*对应atomic的compare_exchange_strong 和 compare_exchange_weak,
这两个版本的区别是:Weak版本如果数据符合条件被修改,其也可能返回false,
就好像不符合修改状态一致;而Strong版本不会有这个问题,但在某些平台上
Strong版本比Weak版本慢;绝大多数情况下,我们应该优先选择使用Strong版本;*/
自旋锁:是指当一个线程在获取锁的时候,如果已经被其它线程获取,那么该线程将循环等待,然后不断判断锁是否能够被获取成功,知道获取到锁才会退出循环,如果不引入中断机制,会有大量计算资源浪费到轮询本身上;
对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。
参考:C++并发编程 | CAS的基本原理剖析(无锁编程、无锁数据结构)