Go为什么不支持可重入锁呢?

先来看看什么是可重入锁,以下是维基百科的定义。

计算机科学中,可重入互斥锁(英语: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 互斥锁没有支持可重入锁的设计,这样就不用关心递归层级等复杂的流程了。加锁解锁中间做了什么一目了然。

This entry was posted in Go and tagged . Bookmark the permalink.

发表评论

邮箱地址不会被公开。 必填项已用*标注