在之前我们通过 sync.Mutex 实现了多个协程有顺序的执行,在这里再次深入了解一下 sync.Mutex 。
作用
sync.Mutex 用于保证同时只有一个 Goroutine 能接触到变量或代码块,避免多个协程之间产生冲突。
如何使用
如下代码,确保多个协程在同一时刻只有一个协程能够对 protecting 执行加一操作。否则最后的计算结果将无法控制。
import (
"fmt"
"sync"
"time"
)
var mu sync.Mutex
var protecting uint
func do() {
mu.Lock()
protecting = protecting + 1
mu.Unlock()
}
func main() {
count := 1000
for i := 0; i < count; i++ {
go do()
}
time.Sleep(3000)
fmt.Printf("this value is %d", protecting)
}
注意事项
要随机解锁互斥锁,必要时使用defer语句
如上代码 我们可以将代码修改为:
func do() {
mu.Lock()
defer mu.Unlock()
protecting = protecting + 1
}
在锁定之后马上定义 defer 函数,不管后续代码是否出现异常(非运行时恐慌),都会将锁解锁。
不要对尚未锁定或者已解锁的互斥锁解锁
对尚未锁定或者已解锁的互斥锁解锁会抛出运行时恐慌 panic,这种情况下 defer 函数是无法恢复的,程序会直接崩溃
不要在函数之间直接传递互斥锁
原因是,互斥锁的基本类型实际上是结构体,这就表明在函数中传递时是属于值传递的,实际上是传递的它的副本。
如下代码:
func do(mu sync.Mutex) {
mu.Unlock()
fmt.Printf("unlocked in func do")
}
func main() {
var mu sync.Mutex
mu.Lock()
do(mu)
//再次尝试加锁
mu.Lock()
fmt.Printf("relock success")
}
运行后会爆出 panic ,提示如下信息
unlocked in func do
fatal error: all goroutines are asleep - deadlock!
原因就是 mu 的锁实际上还没有解锁,我们在方法中解锁的之不是他的一个副本,然而我们之后又在方法中进行了第二次加锁,导致死锁。
对此我们在传递互斥锁的时候,要传递它的指针:
func do(mu *sync.Mutex) {
mu.Unlock()
fmt.Printf("unlocked in func do\n")
}
func main() {
var mu sync.Mutex
mu.Lock()
do(&mu)
//再次尝试加锁
mu.Lock()
fmt.Printf("relock success\n")
}
-----------------------------
unlocked in func do
relock success
sync.RWMutex
sync.RWMutex 是读/写互斥锁,其获取锁的逻辑是:
- 当有协程获取到 RWMutex 的写锁时,其他协程无法获取 RWMutex 的读锁和写锁
- 当有些成获取到 RWMutex 的读锁时,其他协程无法获取 RWMutex 的写锁,但是可以获取到读锁
总结来说就是,RWMutex 的写操作之间互斥,写操作和读操作之间也是互斥的。
当写锁被解锁时,会唤醒所有等待读锁的协程,正常情况下所有等待读锁的协程都将获取到读锁。
当读锁被解锁时,如果没有任何协程持有读锁,那么会唤醒所有等待写锁的协程,并最终只会有一个(等待时间最长的)协程获取到写锁的锁定。