探索锁和可重入锁之间的区别,理解Golang的独特立场,并在系统设计中导航可重入锁的复杂性。
Love is a friendship set to music.
— Joseph Campbell
1.1 锁与可重入锁的概念
解释锁与可重入锁,它是如何工作的?
锁是一个并发控制概念。当某个线程A已经持有了一个锁,当线程B尝试进入被这个锁保护的代码段的时候,就会被阻塞。由此可见锁的操作粒度是“线程”,而不是调用。
而“可重入”指的是某个线程已经获得某个锁,可以再次获取这个锁而不会出现死锁。这就意味着同一个线程再次进入同步代码的时候,可以使用已经获取到的锁,这就是可重入锁。
可重入锁的主要使用场景是线程需要多次进入临界区代码。这个直观的需求就要求锁必须是可重入的,也就是所谓的递归锁。
1.2 Golang 不支持互斥锁
Golang 不支持可重入锁,一些代码示例,帮助理解
在Java中,内置锁(synchronize)和ReentrantLock都是可重入锁。用Java 实现以下逻辑的代码不会有问题。
1 | public static void main(String[]args) { |
但在Golang中,类似的代码将会在产生死锁!
1 | func main() { |
Run it:Better Go Playground
让我们从读写锁的角度更深一步去理解一下:Golang中只有互斥锁,而所有互斥的读写锁的特点是什么?
- 读与读之间不互斥
- 读与写、写与写之间互斥
既然读锁之间是不互斥,也就是可加两次读锁,那么读锁必然是可重入
的,这里就不具体举例了。但是读锁和写锁是互斥的,而且Golang 不支持可重入,习惯于Java等语言的人用Golang时就很可能遇到麻烦,以下构造了一段很可能出现的问题的代码。
1 | func main() { |
Run it: Better Go Playground
这段代码按①、②、③顺序执行,第②段写锁需要等第①个读锁释放,第③段读锁需要等第②段写锁释放,最终就是一个死锁的逻辑。
而同样的逻辑在 Java 中就没有问题,感兴趣可以动手去试试。说到这里,有部分人可能会怀疑Golang 这么做合理吗?为什么会这样实现呢?毕竟从直觉来说:
- 一个协程(或线程)已经获取到了读锁,别的协程(线程)获取写锁时必然需要等待读锁的释放。既然这个协程(或线程)已经拥有了这个读锁,那么为什么再次获取读锁时需要管别的写锁是否等待呢?
这就需要我们了解一下Golang排除可重入锁的设计原则了
1.3 Golang排除可重入锁的哲学
为什么Golang排除可重入锁的支持。
要回答上问的疑问,我们必须要先了解 Golang 互斥锁的设计原则,Golang 在互斥锁设计上会遵守这几个原则。如下:
- 在调用
mutex.Lock
方法时,要保证这些变量的不变性保持,不会在后续的过程中被破坏。 - 在调用
mu.Unlock
方法时,要保证:- 程序不再需要依赖那些不变量。
- 如果程序在互斥锁加锁期间破坏了它们,则需要确保已经恢复了它们。
以可重入的角度我们可以举个例子:
1 | func F() { |
在 F
方法中调用 mu.Lock
方法加上了锁。如果支持可重入锁,接着就会进入到 G
方法中。
此时就会有一个致命的问题,你不知道 F
和 G
方法加锁后是不是做了什么事情,从而导致破坏了一些不变量。
Experimenting with GO 有更多的细节,我理解其中的重点如下:
Recursive mutexes do not protect invariants.
Mutexes have only one job, and recursive mutexes don’t do it.
1.4 Golang实现可重入锁
既然我们知道了Golang 互斥锁的设计哲学,我们不妨多想一步,如果实现可重入锁,你会怎么做?
目前我们了解,Go语言的mutex只记录了加锁状态,没有记录锁的所有者,所以不支持可重入, 参考Java 的实现,我们可以抽象出实现一个可重入锁需要满足的两点:
- 记住持有锁的协程
- 统计重入的次数
统计重入的次数很容易实现,如果能将锁与Gorouting ID绑定,我们就能满足第一点,那么我们理论上就可以实现可重入锁。但是我觉得并没有太多实现的必要,因为至少 Go 语言团队目前并未提供支持。
我认为针对一些场景,更好的去使用互斥锁会对业务有更大帮助,而不是去依赖“可重入锁”。毕竟可重入锁也可能会引入一些新的问题。
1.5 可重入锁的潜在问题
了解可能影响Golang决策的可重入锁的潜在问题或陷阱。
潜在问题:
- 过度的锁嵌套浪费性能。
- 潜在的死锁风险,例如忘记释放锁。
- 实现与使用增加了复杂度。
因此,当你想要使用可重入锁时,我们需要仔细考虑其优势与劣势,以及是否真正需要其可重入特性。
而在Golang语言的世界中,我认为我们应该去遵守Golang的设计哲学。
1.6 参考
这不会又是一个Go的BUG吧?-腾讯云开发者社区-腾讯云
Go实现可重入锁的两种办法 - 掘金
Experimenting with GO
更多该系列文章,参考medium链接:
https://wesley-wei.medium.com/list/you-should-know-in-golang-e9491363cd9a
English post: https://programmerscareer.com/golang-reentry-lock/
作者:Wesley Wei – Twitter Wesley Wei – Medium
注意:原文在 2024-07-04 01:02 时创作于 https://programmerscareer.com/golang-reentry-lock/. 本文为作者原创,转载请注明出处。
评论