写点什么

编程基础: 硬件同步原语

用户头像
正向成长
关注
发布于: 3 小时前
编程基础:硬件同步原语

在并发编程中,我们常常使用锁来保护共享资源,这样会导致性能上的损失。在一些情况下,可以采用硬件同步原语来替代锁,在确保数据安全性的同时,也可以获取更好的性能。


硬件同步原语(Atomic Hardware Primitives)由计算机硬件(即 CPU 提供的实现)提供的一组原子操作,较常用的原语主要是

  • CAS(Compare and Swap)

  • FAA(Fetch and And)


CAS 原语

CAS 实现的伪码:

<< atomic >>function cas(p : pointer to int, old : int, new : int) returns bool {    if *p ≠ old {        return false    }    *p ← new    return true}
复制代码

ABA 问题

假设存在两个线程 P1 和 P2,它们都会对共享变量 A 操作,在下面的场景中变回存在 ABA 问题:

  • P1 从共享内存中读取了 A 当前的值

  • P2 开始执行,它将 A 的值修改为 B,并且在 P1 执行前又修改为 A

  • P1 又开始执行,它从共享内存读取到的值依然是 A,好像没有改变似的,实际上 A 编程 B 又变成 A。


针对 ABA 问题,Java 提供了AtomicStampedReference通过控制变量的版本来实现确保 CAS 的正确性。


优缺点

CAS 可以实现高效的原子操作,却存在以下一些问题:

  • 如果 CAS 失败,反复重试赋值方式,会比较消耗 CPU 资源。如果赋值不成功,会无等待立即进入下一轮循环,如果线程间碰撞频繁,反复重试,重试线程会占用大量的 CPU 时间。

  • 只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁来保证原子性。

  • 存在 ABA 问题。


FAA 原语

FAA 伪码:


<< atomic >>function faa(p : pointer to int, inc : int) returns int { int value <- *location *p <- value + inc return value}
复制代码

FAA 原语的操作是先获取变量的当前值,之后进行计算,更新数据。FAA 原语的计算逻辑只局限于简单的加减法,而 CAS 原语的使用范围更广。


下面是[1]给出的 Go 语言实现的 CAS 和 FAA 实现:

func transferCas(balance *int32, amount int, done chan bool) {  for {    old := atomic.LoadInt32(balance)    new := old + int32(amount)    if atomic.CompareAndSwapInt32(balance, old, new) {      break    }  }  done <- true}
复制代码


func transferFaa(balance *int32, amount int, done chan bool) {  atomic.AddInt32(balance, int32(amount))  done <- true}
复制代码


参考资料

  1. 极客时间:如何用硬件同步原语(CAS)替代锁

  2. 知乎:面试必问的CAS,你懂了吗?


发布于: 3 小时前阅读数: 4
用户头像

正向成长

关注

正向成长 2018.08.06 加入

想要坚定地做大规模数据处理(流数据方向),希望结合结合批处理的传统处理方式,以及之后流批混合处理方向进行学习和记录。

评论

发布
暂无评论
编程基础:硬件同步原语