go - Go中的递归锁定

标签 go

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

最佳答案

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

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

作为上述主张的间接“证明”:对于涉及互斥体的/某些常见情况,递归锁是否是一种常见/正确的方法,它迟早会包含在标准库中。

最后,最后但同样重要的是:Go 开发团队的 Russ Cox 在这里写的 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 - Go中的递归锁定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14670979/

相关文章:

go - 如何使用 GopherJS 生成的 JavaScript 包中的函数?

reflection - 如何打开 reflect.Type?

go - 将 JSON 解码为结构 - 列表中的列表类型?

go - 如何从 map 界面解析特定键?

golang 中的 HTTP 服务器与客户端请求

go - 解码动态 YAML 以映射结构

go - 使用 GOLANG V2 API 在 Google BigQuery 中进行参数化查询

reflection - 解决反射开销的最佳方法是什么?

go - 如何在眼镜蛇应用程序的帮助屏幕中隐藏别名

go - 关于 Go 语法的困惑