Go中的递归锁定 [英] Recursive locking in Go

查看:19
本文介绍了Go中的递归锁定的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Go 的 sync 包有一个 Mutex.不幸的是,它不是递归的.在 Go 中实现递归锁的最佳方式是什么?

解决方案

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

恕我直言,在 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()……做点事……G()……多做点事……mu.Unlock()}功能 G() {mu.Lock()……做点事……mu.Unlock()}

<块引用>

通常,当对 mu.Lock 的调用返回时,调用代码现在可以假设受保护的不变量成立,直到它调用 mu.Unlock.

递归互斥体实现将使 G 的 mu.Lock和 mu.Unlock 调用在 F 中调用时是无操作的或当前线程已经拥有 mu 的任何其他上下文.如果 mu 使用了这样的实现,那么当 mu.Lock在 G 内部返回,不变量可能成立,也可能不成立.这取决于F 在打电话给 G 之前做了什么. 也许 F 甚至没有意识到G 需要那些不变量并且已经破坏了它们(完全可能,尤其是在复杂的代码中).

递归互斥锁不保护不变量.互斥锁只有一项工作,递归互斥锁没有.

它们有更简单的问题,比如你写的

 func F() {mu.Lock()……做点事}

<块引用>

您永远不会在单线程测试中发现错误.但这只是更大问题的一个特例,这是他们根本不提供任何保证互斥锁旨在保护的不变量.

如果需要实现可以调用的功能持有或不持有互斥锁,最清楚的事情就是写两个版本.例如,代替上面的G,你可以写:

//在 mu 已经持有的情况下被调用.//调用者必须小心确保...功能 g() {……做点事……}功能 G() {mu.Lock()G()mu.Unlock()}

<块引用>

或者如果它们都未导出,则使用 g 和 gLocked.

我确信我们最终会需要 TryLock;随意地为此向我们发送 CL.超时锁定似乎不太重要但如果有一个干净的实现(我不知道一个)那么也许就可以了.请不要发送 CL实现递归互斥.

递归互斥只是一个错误,无非一个舒适的虫子之家.

罗斯

Go's sync package has a Mutex. Unfortunately it's not recursive. What's the best way to implement recursive locks in Go?

解决方案

I'm sorry to not answer your question directly:

IMHO, the best way how to implement recursive locks in Go is to not implement them, but rather redesign your code to not need them in the first place. It's probable, I think, that the desire for them indicates a wrong approach to some (unknown here) problem is being used.

As an indirect "proof" of the above claim: Would a recursive lock be a common/correct approach to the/some usual situations involving mutexes, it would be sooner or later included in the standard library.

And finally, last but not least: What Russ Cox from the Go development team wrote here https://groups.google.com/d/msg/golang-nuts/XqW1qcuZgKg/Ui3nQkeLV80J:

Recursive (aka reentrant) mutexes are a bad idea. The fundamental reason to use a mutex is that mutexes protect invariants, perhaps internal invariants like "p.Prev.Next == p for all elements of the ring", or perhaps external invariants like "my local variable x is equal to p.Prev."

Locking a mutex asserts "I need the invariants to hold" and perhaps "I will temporarily break those invariants." Releasing the mutex asserts "I no longer depend on those invariants" and "If I broke them, I have restored them."

Understanding that mutexes protect invariants is essential to identifying where mutexes are needed and where they are not. For example, does a shared counter updated with atomic increment and decrement instructions need a mutex? It depends on the invariants. If the only invariant is that the counter has value i - d after i increments and d decrements, then the atmocity of the instructions ensures the invariants; no mutex is needed. But if the counter must be in sync with some other data structure (perhaps it counts the number of elements on a list), then the atomicity of the individual operations is not enough. Something else, often a mutex, must protect the higher-level invariant. This is the reason that operations on maps in Go are not guaranteed to be atomic: it would add expense without benefit in typical cases.

Let's take a look at recursive mutexes. Suppose we have code like this:

     func F() {
             mu.Lock()
             ... do some stuff ...
             G()
             ... do some more stuff ...
             mu.Unlock()
     }

     func G() {
             mu.Lock()
             ... do some stuff ...
             mu.Unlock()
     }

Normally, when a call to mu.Lock returns, the calling code can now assume that the protected invariants hold, until it calls mu.Unlock.

A recursive mutex implementation would make G's mu.Lock and mu.Unlock calls be no-ops when called from within F or any other context where the current thread already holds mu. If mu used such an implementation, then when mu.Lock returns inside G, the invariants may or may not hold. It depends on what F has done before calling G. Maybe F didn't even realize that G needed those invariants and has broken them (entirely possible, especially in complex code).

Recursive mutexes do not protect invariants. Mutexes have only one job, and recursive mutexes don't do it.

There are simpler problems with them, like if you wrote

     func F() {
             mu.Lock()
             ... do some stuff
     }

you'd never find the bug in single-threaded testing. But that's just a special case of the bigger problem, which is that they provide no guarantees at all about the invariants that the mutex is meant to protect.

If you need to implement functionality that can be called with or without holding a mutex, the clearest thing to do is to write two versions. For example, instead of the above G, you could write:

     // 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()
     }

or if they're both unexported, g and gLocked.

I am sure that we'll need TryLock eventually; feel free to send us a CL for that. Lock with timeout seems less essential but if there were a clean implementation (I don't know of one) then maybe it would be okay. Please don't send a CL that implements recursive mutexes.

Recursive mutexes are just a mistake, nothing more than a comfortable home for bugs.

Russ

这篇关于Go中的递归锁定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆