-
参考文章
Go语言并发之道
Go并发编程 -
互斥锁、读写锁
临界区: 程序中需要独占访问共享资源的区域-
互斥锁
- 锁住临界区
- 由 sync.Mutex结构体类型表示
- 只有两个公开的
指针方法
: Lock() 和 Unlock() - sync.Mutex类型的零值表示未被锁定的互斥量
- 如果锁定了一个已锁定的互斥锁,重复锁定操作的goroutine将被阻塞
- 如果给一个未锁定的互斥锁进行解锁操作时,就会panic,不可恢复
- 首次使用后,互斥锁不能复制
- 锁可以被多个协程共享,但建议同一个互斥锁的加锁解锁在同一层次
-
读写锁
- 锁住临界区
- 由 sync.RWMutex结构体类型表示
- 有两对方法:
Lock() 和 Unlock() 写操作的锁定和解锁
RLock() 和 RUnlock() 读操作的锁定和解锁 - sync.Mutex类型的零值表示未被锁定的互斥量
- 与互斥锁的不同:分别对读操作和写操作进行锁定和解锁操作
- 没有锁定的读写两类锁,进行对应的解锁时,都会被panic,不可恢复
- 首次使用后,读写锁不能复制
- 写独占,读共享,写锁优先级高
-
-
Demo示例加深印象
-
互斥锁
- 如果锁定一个已经锁定的互斥锁,重复锁定操作的goroutine将会被阻塞
var lock sync.Mutex fmt.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.Mutex mutex.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.Mutex for 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.Mutex for 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)
多次运行之后会发现,值的顺序是不定的。
这里再埋个坑:无论运行多少次都会发现没有出现抢占的异常
- 如果锁定一个已经锁定的互斥锁,重复锁定操作的goroutine将会被阻塞
-
读写锁
- 有两对方法:
- 写独占,读共享,写锁优先级高
var rwm sync.RWMutex for 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]
可以发现,当写锁锁定未解锁时,读锁的锁定是会阻塞的。
写独占,读共享
完整的意思是:- 多个写操作之间是互斥的
- 写操作与读操作之间也是互斥的
- 多个读操作之间不是互斥的
-
Golang从入门到放弃200711--Lock
最新推荐文章于 2024-04-10 00:10:58 发布