介绍
Golang 中的 sync 包中实现了两种锁,一种是 Mutex(互斥锁),另一种是RWMutex(读写锁),其中 RWMutex 是基于 Mutex 实现的
Mutex
Mutex 是互斥锁,Lock() 是加锁,Unlock() 是解锁,在一个 goruntine 对 Mutex 进行上锁之后,其他的 goruntine 只能等到该 goruntine 对该 Mutex 解锁之后才能在对其进行加锁操作。
示例如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
package main
import (
"fmt"
"sync"
"time"
)
func main() {
// 保证四个goroutine执行完之前主函数不会退出
wg := sync.WaitGroup{}
wg.Add(4)
lock := sync.Mutex{}
fmt.Println("This lock is locked")
lock.Lock()
for i := 0; i < 4; i++ {
go func(i int, wg *sync.WaitGroup) {
fmt.Println(i, "Not lock")
lock.Lock()
fmt.Println(i, "get the lock")
time.Sleep(time.Second)
fmt.Println(i, "unlock the lock")
lock.Unlock()
wg.Done()
}(i, &wg)
}
time.Sleep(time.Second)
fmt.Println("This lock is unlock")
lock.Unlock()
wg.Wait()
}
|
执行结果
使用 mutex 注意事项如下
-
在一个 goruntine 对 Mutex 进行上锁之后,其他的 goruntine 只能等到该 goruntine 对该 Mutex 解锁之后才能在对其进行加锁操作
-
一个 goruntine 对 Mutex 加锁之后,不能再继续对其进行加锁,需要解锁之后才能在进行加锁,否则会形成死锁,报错如下
fatal error: all goroutines are asleep - deadlock!
-
在 Lock() 之前使用 Unlock() 会导致 panic 异常,信息如下
fatal error: sync: unlock of unlocked mutex
-
适用于读写不确定,并且只有一个读或者写的场景
RWMutex
RWMutex 是读写锁, 可以加多个读写和一个写锁。在读锁被占用的时候会阻止写锁,但是不会阻止读锁,多个 goruntine 可以共享读锁, 写锁会阻止写锁和读写,写锁一次只能被一个 goruntine 所占用。
RLock() 是加读锁, RUnlock() 是解读锁。
示例如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package main
import (
"fmt"
"sync"
"time"
)
func main() {
wg := sync.WaitGroup{}
wg.Add(4)
lock := sync.RWMutex{}
fmt.Println("This lock is locked")
lock.RLock()
for i := 0; i < 4; i++ {
go func(i int, wg *sync.WaitGroup) {
lock.RLock()
fmt.Println(i, "get the lock")
time.Sleep(time.Second)
fmt.Println(i, "unlock the lock")
lock.RUnlock()
wg.Done()
}(i, &wg)
}
time.Sleep(time.Second)
fmt.Println("This lock is unlock")
lock.RUnlock()
wg.Wait()
}
|
结果
可以看到在主函数释放读锁之前,其他的 goruntine 也一样可以获取到读锁
注意事项:
- RLock() 加读锁时,如果存在写锁,则无法加读锁;当只有读锁或者没有锁时,可以加读锁,读锁可以加载多个
- RUnlock() 解读锁,RUnlock() 撤销当前 RLock() 调用,对于其他同时存在的读锁没有效果
- 在没有读锁的情况下调用 RUnlock() 会导致 panic 错误
- RUnlock() 的个数不得多余 RLock(),否则会导致 panic 错误
至于写锁其实就和 Mutex 的用法一样,唯一的不同就是如果在加写锁之前已经有其他的读锁和写锁,则 Lock() 会阻塞直到该锁可用,为确保该锁可用,已经阻塞的 Lock() 调用会从获得的锁中排除新的读取器,即写锁权限高于读锁,有写锁时优先进行写锁定。
RWMutex使用错误示范如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package main
import "sync"
func main() {
rwMutex := sync.RWMutex{}
// 1. 未解写锁之前加写锁,发生死锁
rwMutex.Lock()
rwMutex.Lock()
// 2. 未解写锁之前加读锁,发生死锁
rwMutex.Lock()
rwMutex.RLock()
// 3. 在加读锁之前解读锁,抛出panic
rwMutex.RUnlock()
//4. 解读锁个数大于加读锁个数,抛出panic
rwmutex.RLock()
rwmutex.RLock()
rwmutex.RUnlock()
rwmutex.RUnlock()
rwmutex.RUnlock()
}
|