CAS机制

悲观锁和乐观锁

悲观锁认为访问的资源肯定会被别人访问,为了保险起见,会对访问的资源先加锁,等到不再访问的时候再解锁。

乐观锁却不这么想,但是资源只有一份,你乐观锁不加锁操作并不代表就可以直接访问资源而不管有没有人访问,所以乐观锁也有“加锁”机制,但是和悲观锁的行为不同。悲观锁是实际加锁和解锁来达到原子性,而乐观锁在正式更新数据之前会检查数据是否被其他线程改变过,如果未被其他线程改变过就将共享变量更新成最新值,如果发现共享变量已经被其他线程更新过了,就重试,直到成功为止。整个过程不涉及加锁和解锁操作,但却已实现原子性。

原子锁的优点:

  • 可以保证变量操作的原子性
  • 并发量不是很高的情况下,使用 CAS 机制比使用锁机制效率更高
  • 在线程对共享资源占用时间较短的情况下,使用 CAS 机制效率也会较高
CAS.jpg

如上图中,主存中保存 V 值,线程中要使用 V 值要先从主存中读取V值到线程的工作内存 A 中,然后计算后变成 B 值,最后再把B值写回到内存 V 值中。多个线程共用 V 值都是如此操作。CAS 的核心是在将 B 值写入到 V 之前要比较 A 值和 V 值是否相同,如果不相同证明此时 V 值已经被其他线程改变,重新将 V 值赋给 A,并重新计算得到 B,如果相同,则将 B 值赋给 V。

1
bool compare_exchange_strong (T& expected, T val, memory_order sync = memory_order_seq_cst)

expected 是期望值,val 是打算替换的值。

调用 compare_exchange_strong 者的值如果和 期望值相同(返回 true),就把调用者的值替换为 val。

否则,就把调用者的值替换为 期望值。

不足

ABA问题

CAS 在操作的时候会检查变量的值是否被更改过,如果没有则更新值,但是带来一个问题,最开始的值是 A,接着变成B,最后又变成了 A。经过检查这个值确实没有修改过,因为最后的值还是 A,但是实际上这个值确实已经被修改过了。为了解决这个问题,在每次进行操作的时候加上一个版本号,每次操作的就是两个值,一个版本号和某个值,A——>B——>A 问题就变成了 1A——>2B——>3A。

可能会消耗较高的CPU

看起来 CAS 比锁的效率高,从阻塞机制变成了非阻塞机制,减少了线程之间等待的时间。每个方法不能绝对的比另一个好,在线程之间竞争程度大的时候,如果使用 CAS,每次都有很多的线程在竞争,也就是说 CAS 机制不能更新成功。这种情况下 CAS 机制会一直重试,这样就会比较耗费 CPU。因此可以看出,如果线程之间竞争程度小,使用 CAS 是一个很好的选择;但是如果竞争很大,使用锁可能是个更好的选择

不能保证代码块的原子性

只能保证原子变量操作的原子性,可不能保证代码块的原子性(锁是可以做到的,而且随意块的大小都可以做到)。

参考链接

并发编程的基石——CAS机制