乐观锁和悲观锁

当程序中可能出现并发的情况时,就需要通过一定的手段来保证在并发情况下数据的准确性,通过这种手段保证了当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的。这种手段就叫做并发控制。并发控制的目的是保证一个用户的工作不会对另一个用户的工作产生不合理的影响。

实现并发控制的主要手段大致可以分为乐观并发控制和悲观并发控制两种。

基本概念

  • 乐观锁: 乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间,是否有其他人修改了数据,如果有修改数据,则放弃操作,否则执行操作。

  • 悲观锁: 悲观锁在操作数据时比较悲观,认为别人会搞事情,修改数据。因此为了独享数据,会对数据上锁,直到操作完成后才会释放锁;上锁期间,其他人不能修改数据。

实现方式

乐观锁的实现方式主要有两种:CAS机制和版本号机制

CAS(Compare And Swap)

CAS是由CPU支持的原子操作,其原子性是在硬件层面进行保证的。

CAS(add, old, new)操作逻辑如下:如果内存位置add的值等于预期的old值,则将add的值更新为new值,否则不进行任何操作。

许多CAS的操作是自旋的,在golang里,如果操作不成功,会发生自旋一直重试,直到操作成功为止。

1
2
3
4
5
6
7
8
for {
if atomic.CompareAndSwapInt64(add, old, new) {
// 获取锁,直接返回执行业务
break
}

// 否则发生自旋
}

CAS缺点

  • ABA问题

  • 高竞争下的开销问题,CAS失败的会一直重试,CPU开销大

  • CAS只能保证单个变量操作的原子性

版本号机制

除了CAS,版本号机制也可以用来实现乐观锁。版本号机制的基本思路是在数据中增加一个字段version,表示该数据的版本号,每当数据被修改,版本号加1。当某个线程查询数据时,将该数据的版本号一起查出来;当该线程更新数据时,判断当前版本号与之前读取的版本号是否一致,如果一致才进行操作。


参考: 乐观锁和悲观锁吗,你确定分清楚了吗? | 码农俱乐部 - Golang中国 - Go语言中文社区