Golang中的锁机制主要包含互斥锁和读写锁
互斥锁
互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )。
func mutex() {
var mu
sync.Mutex mu.Lock()
fmt.Println("locked")
mu.Unlock()
}
defer实现
func mutex() {
var mu
sync.Mutex mu.Lock()
defer mu.Unlock()
fmt.Println("locked")
}
【互斥锁的特点】:
原子性:把一个互斥量锁定为一个原子操作,这意味着操作系统(或pthread函数库)保证了如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量;
唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量;
非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。
互斥锁是开箱即用的,只需要申明sync.Mutex即可直接使用
互斥锁应该是成对出现,在同步语句不可以再对锁加锁,看下面的示例:
func mutex() {
var mu
sync.Mutex mu.Lock()
fmt.Println("parent locked")
mu.Lock()
fmt.Println("sub locked")
mu.Unlock()
mu.Unlock()
}
此时则会出现fatal error: all goroutines are asleep - deadlock!错误
读写锁
读写锁和互斥锁不同之处在于,可以分别针对读操作和写操作进行分别锁定,这样对于性能有一定的提升。 读写锁,对于多个写操作,以及写操作和读操作之前都是互斥的这一点基本等同于互斥锁。 但是对于同时多个读操作之前却非互斥关系,这也是相读写锁性能高于互斥锁的主要原因。
- 特点
开箱即用
var rwm = sync.RWMutex
- 写锁定和写解锁
rwm.Lock()
rwm.Unlock() - 读锁定和读解锁
rwm.RLock()
rwm.RUnlock() - 读写锁的读锁和写锁不能交叉相互解锁,否则会发生panic
func rwMutex() {
var rwm sync.RWMutex
rwm.Lock()
fmt.Println("locked")
rwm.RUnlock()
}
- 对于读写锁,同一资源可以同时有多个读锁定
在goroutine中,写解锁会试图唤醒所有想要进行读锁定而被阻塞的goroutine。
而读解锁会在已无任何读锁定的情况下,试图唤醒一个想进行写锁定而被阻塞的goroutine。
func rwMutex() {
var rwm sync.RWMutex
for i := 0; i <= 2; i++ {
go func(i int) {
fmt.Printf("go(%d) start lock\n", i)
rwm.RLock()
fmt.Printf("go(%d) locked\n", i)
time.Sleep(time.Second * 2)
rwm.RUnlock()
fmt.Printf("go(%d) unlock\n", i)
}(i)
}
// 先sleep一小会,保证for的goroutine都会执行
time.Sleep(time.Microsecond * 100)
fmt.Println("main start lock")
// 当子进程都执行时,且子进程所有的资源都已经Unlock了
// 父进程才会执行
rwm.Lock()
fmt.Println("main locked")
time.Sleep(time.Second)
rwm.Unlock()
}
参考
互斥锁、条件锁、读写锁以及自旋锁:https://www.zhihu.com/question/66733477
https://www.cnblogs.com/cangqinglang/p/13328217.html
https://laravelacademy.org/post/19901.html