-
参考文章
Go语言并发之道
Go并发编程 -
互斥锁、读写锁
临界区: 程序中需要独占访问共享资源的区域互斥锁
- 锁住临界区
- 由 sync.Mutex结构体类型表示
- 只有两个公开的
指针方法
: Lock() 和 Unlock()
- sync.Mutex类型的零值表示未被锁定的互斥量
- 如果锁定了一个已锁定的互斥锁,重复锁定操作的goroutine将被阻塞
- 如果给一个未锁定的互斥锁进行解锁操作时,就会panic,不可恢复
- 首次使用后,互斥锁不能复制
- 锁可以被多个协程共享,但建议同一个互斥锁的加锁解锁在同一层次
-
读写锁
- 锁住临界区
- 由 sync.RWMutex结构体类型表示
- 有两对方法:
Lock() 和 Unlock() 写操作的锁定和解锁
RLock() 和 RUnlock() 读操作的锁定和解锁 - sync.Mutex类型的零值表示未被锁定的互斥量
- 与互斥锁的不同:分别对读操作和写操作进行锁定和解锁操作
- 没有锁定的读写两类锁,进行对应的解锁时,都会被panic,不可恢复
- 首次使用后,读写锁不能复制
- 写独占,读共享,写锁优先级高
Demo示例加深印象
互斥锁
- 如果锁定一个已经锁定的互斥锁,重复锁定操作的goroutine将会被阻塞
var lock sync.Mutexfmt.Println(\"Lock the lock.(main)\")lock.Lock()fmt.Println(\"The lock is locked.(main)\")for i := 1; i <= 3; i++ {go func(i int) {fmt.Printf(\"Lock the lock.(g%d)\\n\", i)lock.Lock()//defer lock.Unlock() 加不加之后结果比较fmt.Printf(\"The lock is locked.(g%d)\\n\", i)}(i)}time.Sleep(time.Second)fmt.Println(\"Unlock the lock.(main)\")lock.Unlock()fmt.Println(\"The lock is unlocked.(main)\")time.Sleep(time.Second)
运行之后的结果:
Lock the lock.(main) The lock is locked.(main) Lock the lock.(g3) Lock the lock.(g2) Lock the lock.(g1) Unlock the lock.(main) The lock is unlocked.(main) The lock is locked.(g3)
从结果上可以看出:
3个goroutine上的都已经提示已经锁住,当未解锁时,只有打印出一个goroutine的结果。如果把注释处的
defer lock.Unlock()
加入代码,就会发现3个
goroutine都会打印出来。
-
如果给一个未锁定的互斥锁进行解锁操作时,就会panic,不可恢复
defer func() {fmt.Println(\"Try to recover the panic.\")if p := recover(); p != nil{fmt.Printf(\"Recovered the panic(%#v).\\n\",p)}}()var mutex sync.Mutexmutex.Lock()mutex.Unlock()fmt.Println(\"Unlock the lock.\")mutex.Unlock()
运行结果:
Unlock the lock again.fatal error: sync: unlock of unlocked mutex
可以看到,就算试图去恢复panic,程序依旧终止了
-
首次使用后,互斥锁不能复制
var lock sync.Mutexfor i := 0; i < 5; i++ {go func() {lock.Lock()j++fmt.Printf(\"j = %d\\t\", j)lock.Unlock()}()}time.Sleep(1 * time.Second)
运行的结果:
j = 1 j = 2 j = 3 j = 4 j = 5
多次运行之后仍然可以发现,j的结果是顺序打印的。
更改下代码,让sync.Mutex值传入到goroutine
var lock sync.Mutexfor i := 0; i < 5; i++ {go func(lock sync.Mutex) {lock.Lock()j++fmt.Printf(\"j = %d\\t\", j)lock.Unlock()}(lock)}time.Sleep(1 * time.Second)
多次运行之后会发现,值的顺序是不定的。
这里再埋个坑:无论运行多少次都会发现没有出现抢占的异常
读写锁
- 有两对方法:
var rwm sync.RWMutexfor i := 0; i < 3; i++ {go func(i int) {fmt.Printf(\"Try to lock for reading...[%d]\\n\", i)rwm.RLock()fmt.Printf(\"Locked for reading:[%d]\\n\", i)time.Sleep(2 * time.Second)fmt.Printf(\"Try to unlock for reading...[%d]\\n\", i)rwm.RUnlock()fmt.Printf(\"Unlocked for reading:[%d]\\n\", i)}(i)}time.Sleep(100 * time.Millisecond) //fmt.Println(\"Try to lock for writing...\")rwm.Lock()fmt.Println(\"Locked for writing.\")
运行结果:
Try to lock for reading...[0]Locked for reading:[0]Try to lock for reading...[1]Locked for reading:[1]Try to lock for reading...[2]Locked for reading:[2]Try to lock for writing...Try to unlock for reading...[1]Unlocked for reading:[1]Try to unlock for reading...[2]Unlocked for reading:[2]Try to unlock for reading...[0]Unlocked for reading:[0]Locked for writing.
直接看运行结果看不出来什么,我把结果分成了两段,从打印时间上有很明显的间
隔。很明显因为goroutine中多次读锁锁住后,在
main
中的
rwm.Lock()
阻塞了
所以,间隔了2s左右后,再次打印出结果goroutine中解锁后的结果。同时写锁成
功锁住,程序不再阻塞,成功结束
把
main
中的
time.Sleep(100 * time.Millisecond)
挪到main的末尾,多次运
行,会有两种结果。其中一种结果是:
Try to lock for writing...Locked for writing.Try to lock for reading...[0]Try to lock for reading...[2]Try to lock for reading...[1]
可以发现,当写锁锁定未解锁时,读锁的锁定是会阻塞的。
写独占,读共享
完整的意思是:
- 多个写操作之间是互斥的
- 写操作与读操作之间也是互斥的
- 多个读操作之间不是互斥的