先来看看什么是可重入锁,以下是维基百科的定义。
计算机科学中,可重入互斥锁(英语:reentrant mutex)是互斥锁的一种,同一线程对其多次加锁不会产生死锁。可重入互斥锁也称递归互斥锁(英语:recursive mutex)或递归锁(英语:recursive lock)。
如果对已经上锁的普通互斥锁进行“加锁”操作,其结果要么失败,要么会阻塞至解锁。而如果换作可重入互斥锁,当且仅当尝试加锁的线程就是持有该锁的线程时,类似的加锁操作就会成功。可重入互斥锁一般都会记录被加锁的次数,只有执行相同次数的解锁操作才会真正解锁。
递归互斥锁解决了普通互斥锁不可重入的问题:如果函数先持有锁,然后执行回调,但回调的内容是调用它自己,就会产生死锁。[1]
Go里面是不支持可重入互斥锁的。看代码:
var mu sync.Mutex
func main() {
mu.Lock()
mu.Lock()
}
运行后会报
fatal error: all goroutines are asleep - deadlock!
Go 设计原则
在工程中使用互斥的根本原因是:为了保护不变量,也可以用于保护内、外部的不变量。
基于此,Go 在互斥锁设计上会遵守这几个原则。如下:
- 在调用
mutex.Lock
方法时,要保证这些变量的不变性保持,不会在后续的过程中被破坏。 - 在调用
mu.Unlock
方法时,要保证:- 程序不再需要依赖那些不变量。
- 如果程序在互斥锁加锁期间破坏了它们,则需要确保已经恢复了它们。
不支持的原因
讲了 Go 自己的设计原则后,那为什么不支持可重入呢?
其实 Russ Cox 于 2010 年在《Experimenting with GO》就给出了答复,认为递归(又称:重入)互斥是个坏主意,这个设计并不好。
总结
Go 互斥锁没有支持可重入锁的设计,这样就不用关心递归层级等复杂的流程了。加锁解锁中间做了什么一目了然。