白话乐观锁与悲观锁

改变一个数值的三个步骤

听起来就像是一个笑话,可是真的是这三步:①把想修改的数值从某个地方取出来;②在取出来的数值修改为期望值;③把修改后的数值保存到原来的地方。

这里面有一个问题,把数值取出来进行修改的时候(做完了①步,正在做②步),如果有另一个过程(进程或线程)对同一个数值进行同样的操作(取值,修改),那么当两个过程都要做③的时候,就肯定有一个过程是白干活的。

怎么办呢?

悲观锁

悲观的锁总认为会发生并发问题,属于保守派

如果想修改一个数值,立马给这个数值上一把锁,标明这个数值正在被修改,谁也不能修改了;然后才开始三步走,在三步走的过程结束以后,再把锁解除。

当有其他过程想要修改同一个数值时,看到了锁就不进行三步走了,而是选择等待;当锁被解除了,自己在数值也加一把锁,然后开始三步走,在三个步骤走完了,也把锁解除。

上面的过程既是悲观锁,总是存在加锁和解除锁的动作。

乐观锁

如果修改数值的时候,并没有其他过程修改同一个数值,那么给它加锁再解除锁岂不是浪费时间浪费金钱。

乐观的锁总认为不会发生并发问题,属于乐天派

如果想修改一个数值,不加锁了,可是怎么保证当自己修改数值的时候没有其他的过程修改同一个数值?有办法!

不加锁,正常进行①②步,在进行③的时候,确认一下数值是否进行了修改,如果被修改过,放弃修改,重新走一遍①②③(或者放弃对数值进行修改)。

上面的过程既是乐观锁,不存在加锁和解除锁的动作。

乐观锁与悲观锁对比

乐观锁没有加锁和解除锁的步骤,直觉上会快一些;但是乐观锁这么做的前提是总认为不会发生并发,如果并发发生的概率很大,重试的次数会增加,这种情况下乐观锁的性能就差很多了。

悲观锁加锁和解除锁的步骤,直觉上会慢一些;但是当有很多进程或者线程对同一个数值进行修改时,能避免大量的重试过程,这种情况下悲观锁的性能相对就很高了。

Golang中的乐观锁与悲观锁

sync/atomic

Golang中有一个 atomic 包,可以在不形成临界区和创建互斥量的情况下完成并发安全的值替换操作,这个包应用的便是乐观锁的原理。

不过这个包只支持int32/int64/uint32/uint64/uintptr这几种数据类型的一些基础操作(增减、交换、载入、存储等)

sync

Golang中的sync包,提供了各种锁,如果使用了这个包,基本上就以悲观锁的工作模式了。

参考