互斥锁
代码示例
package main
import (
"fmt"
"sync"
"time"
)
func main() {
//声明
var mutex sync.Mutex
fmt.Println("Lock the lock. (G0)")
//加锁mutex
mutex.Lock()
fmt.Println("The lock is locked.(G0)")
for i := 1; i < 4; i++ {
go func(i int) {
fmt.Printf("----Lock the lock. (G%d)\n", i)
mutex.Lock()
fmt.Printf("++++The lock is locked. (G%d)\n", i)
}(i)
}
//休息一会,等待打印结果
time.Sleep(time.Second)
fmt.Println("Unlock the lock. (G0)")
//解锁mutex
mutex.Unlock()
fmt.Println("The lock is unlocked. (G0)")
//休息一会,等待打印结果
time.Sleep(time.Second)
Lock the lock. (G0)
// 同一个互斥锁的成对锁定和解锁操作放在同一层次的代码块中。
}
打印结果
Lock the lock. (G0)
The lock is locked.(G0)
----Lock the lock. (G2)
----Lock the lock. (G1)
----Lock the lock. (G3)
Unlock the lock. (G0)
The lock is unlocked. (G0)
++++The lock is locked. (G2) //随机 G1 G2 G3
使用互斥锁的注意事项如下:
- 不要重复锁定互斥锁;
对一个已经被锁定的互斥锁进行锁定,是会立即阻塞当前的goroutine
的。这个goroutine
所执行的流程,会一直停滞在调用该互斥锁的Lock
方法的那行代码上。 - 不要忘记解锁互斥锁,必要时使用defer语句;
应该保证,对于每一个锁定操作,都要有且只有一个对应的解锁操作,避免重复锁定。 - 不要对尚未锁定或者已解锁的互斥锁解锁;
解锁未锁定的互斥锁会立即引发panic
,这种由 Go 语言运行时系统自行抛出的 panic 都属于致命错误,都是无法被恢复的。 - 不要在多个函数之间直接传递互斥锁。
sync.Mutex
是一个结构体类型,属于值类型中的一种。把它传给一个函数、将它从函数中返回、把它赋给其他变量、让它进入某个通道都会导致它的副本的产生。原值和它的副本,以及多个副本之间都是完全独立的,它们都是不同的互斥锁。
建议:尽量避免把一个互斥锁同时用在了多个地方,多个goroutine 争用这把锁增大死锁可能性。而最简单、有效的方式就是让每一个互斥锁都只保护一个临界区或一组相关临界区。
读写锁
读写锁sync.RWMutex
是把对共享资源的“读操作”和“写操作”区别对待了。它可以对这两种操作施加不同程度的保护。换句话说,相比于互斥锁,读写锁可以实现更加细腻的访问控制。
对于某个受到读写锁保护的共享资源,多个写操作不能同时进行,写操作和读操作也不能同时进行,但多个读操作却可以同时进行。