为了账号安全,请及时绑定邮箱和手机立即绑定

Go中的递归锁定

Go中的递归锁定

Go
慕盖茨4494581 2021-05-14 18:13:55
Go的sync包裹中有个Mutex。不幸的是,它不是递归的。在Go中实现递归锁的最佳方法是什么?
查看完整描述

3 回答

?
jeck猫

TA贡献1909条经验 获得超7个赞

很抱歉没有直接回答您的问题:


恕我直言,在Go中实现递归锁的最好方法是不实现它们,而是重新设计代码以一开始就不需要它们。我认为,对它们的渴望很可能表明正在使用对某些(此处未知)问题的错误方法。


作为上述权利要求的间接“证明”:递归锁是否是针对涉及互斥锁的一些常见情况的常用/正确方法,它迟早会包含在标准库中。


最后,最后但并非最不重要的是:Go 开发团队的 Russ Cox 在这里写了什么https://groups.google.com/d/msg/golang-nuts/XqW1qcuZgKg/Ui3nQkeLV80J:


递归(又称可重入)互斥锁是个坏主意。使用互斥锁的根本原因是互斥锁保护不变式,也许是内部不变式,例如“ p.Prev.Next == p代表环的所有元素”,或者是外部不变式,例如“我的局部变量x等于p.Prev 。”


锁定互斥锁会断言“我需要保持不变式”,并且也许“我会暂时破坏这些不变式”。释放互斥量会断言“我不再依赖那些不变式”和“如果我打破了它们,我就将它们还原了”。


了解互斥体保护不变式对于确定需要互斥体和不需要互斥体至关重要。例如,用原子增量和减量指令更新的共享计数器是否需要互斥量?这取决于不变性。如果唯一不变的是计数器在i递增和d递减后具有值i-d,则指令的气氛确保不变。不需要互斥。但是,如果计数器必须与其他数据结构同步(也许它对列表中元素的数量进行计数),则单个操作的原子性是不够的。通常是互斥体的其他某些东西必须保护更高级别的不变式。这就是不能保证Go中的地图操作是原子性的原因:在典型情况下,这样做会增加费用而没有收益。


让我们看一下递归互斥体。假设我们有这样的代码:


     func F() {

             mu.Lock()

             ... do some stuff ...

             G()

             ... do some more stuff ...

             mu.Unlock()

     }


     func G() {

             mu.Lock()

             ... do some stuff ...

             mu.Unlock()

     }

通常,当返回对mu.Lock的调用时,调用代码现在可以假定受保护的不变式成立,直到调用mu.Unlock为止。


从F或当前线程已经拥有mu的任何其他上下文中调用时,递归互斥体实现将使G的mu.Lock和mu.Unlock调用成为no-ops。如果mu使用了这样的实现,则当mu.Lock返回G内部时,不变量可能成立,也可能不成立。这取决于F在调用G之前所做的事情。也许F甚至没有意识到G需要这些不变式并将其打破(完全有可能,尤其是在复杂代码中)。


递归互斥不能保护不变式。互斥锁只有一项工作,而递归互斥锁则不行。


它们有更简单的问题,例如您写的


     func F() {

             mu.Lock()

             ... do some stuff

     }

你永远不会在单线程测试中发现错误。但这只是更大问题的特例,那就是它们根本无法保证互斥量本来要保护的不变量。


如果您需要实现可以在持有或不持有互斥锁的情况下进行调用的功能,那么最清晰的事情就是编写两个版本。例如,代替上面的G,您可以编写:


     // To be called with mu already held.

     // Caller must be careful to ensure that ...

     func g() {

             ... do some stuff ...

     }


     func G() {

             mu.Lock()

             g()

             mu.Unlock()

     }

或如果它们都未导出,则为g和gLocked。


我确信我们最终将需要TryLock;请随时向我们发送一份CL。超时锁定似乎没有那么重要,但是如果有一个干净的实现(我不知道一个实现),那可能就可以了。请不要发送实现递归互斥的CL。


递归互斥体只是一个错误,无非是一个舒适的错误之家。


查看完整回答
反对 回复 2021-05-31
?
慕村9548890

TA贡献1884条经验 获得超4个赞

您可以很容易地从sync.Mutexsync.Cond中进行递归锁定。有关一些想法,请参见此处的附录A。

除了Go运行时不公开goroutine Id的任何概念外。这是为了阻止人们使用goroutine本地存储来做愚蠢的事情,并且可能表明设计师认为如果您需要goroutine ID,那么您做错了。

如果您确实愿意,您当然可以用一点C从运行时中挖掘goroutine ID。您可能想阅读该主题,以了解Go的设计师为什么认为这是一个坏主意。


查看完整回答
反对 回复 2021-05-31
  • 3 回答
  • 0 关注
  • 289 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信