Go语言中乐观锁与悲观锁的具体使用
作者:别人家的孩子zyh
乐观锁和悲观锁是两种思想,用于解决并发场景下的数据竞争问题,本文主要介绍了Go语言中乐观锁与悲观锁的具体使用,具有一定的参考价值,感兴趣的可以了解一下
改变一个数值的三个步骤
- 把想修改的数值从某个地方取出来
- 将取出来的数值修改为期望值
- 把修改后的数值保存到原来的地方
问题
如果在做第2步时,有另一个过程(进程或线程)对同一个数值进行同样的操作(取值、修改),那么当这两个过程都要做第3步的时候,就肯定有一个过程是白干活的。
悲观锁
悲观的锁总认为会发生并发问题,属于保守派。
如果想修改一个数值,立马给这个数值上一把锁,标明这个数值正在被修改,谁也不能修改了;然后才开始三步走,在三步走的过程结束以后,再把锁解除。
当有其他过程想要修改同一个数值时,看到了锁就不进行三步走了,而是选择等待;当锁被解除了,自己在数值也加一把锁,然后开始三步走,在三个步骤走完了,也把锁解除。
乐观锁
乐观的锁总认为不会发生并发问题,属于乐天派。
修改数据时不加锁,正常进行1、2步,在进行第3步的时候,确认一下数值是否进行了修改,如果被修改过,放弃修改,重新走一遍1、2、3步(或者放弃对数值进行修改)。
Go语言中的乐观锁与悲观锁
sync/atomic
Go语言有一个atomic包,可以在不形成临界区和创建互斥量的情况下完成并发安全的值替换操作,这个包应用的便是乐观锁的原理
但是这个包只支持int32/int64/uint32/uint64/uintptr这几种数据类型的一些基础操作,如增减、交换、载入、存储等
sync
Go语言中的sync包提供了各种锁,如果使用了这个包,基本就以悲观锁的工作模式了
go代码示例
package main import ( "fmt" "sync" "sync/atomic" "time" ) var ( x int64 mu sync.Mutex wg sync.WaitGroup ) // 普通函数, 并发不安全 func Add() { x++ wg.Done() } // 互斥锁, 并发安全,性能低于原子操作 func muAdd() { mu.Lock() x++ mu.Unlock() wg.Done() } // 原子操作,并发安全,性能高于互斥锁,只针对go中的一些基本数据类型使用 func AmAdd() { atomic.AddInt64(&x, 1) wg.Done() } func main() { // 原子操作atomic包 // 加锁操作涉及到内核态的上下文切换, 比较耗时,代价高 // 针对基本数据类型我们还可以使用原子操作来保证并发安全 // 因为原子操作是go语言提供的方法,我们在用户态就可以完成,因此性能比加锁操作更好 // go语言的原子操作由内置的库,sync/atomic完成 start := time.Now() for i := 0; i < 10000; i++ { wg.Add(1) go Add() // 普通版Add函数不是并发安全的 // go muAdd() // 加锁版Add函数,是并发安全的, 但是加锁性能开销大 // go AmAdd() // 原子操作版Add函数,是并发安全的,性能优于加锁版 } end := time.Now() wg.Wait() fmt.Println(x) fmt.Println(end.Sub(start)) }
到此这篇关于Go语言中乐观锁与悲观锁的具体使用的文章就介绍到这了,更多相关Go语言 乐观锁与悲观锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!