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

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

问题描述

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

解决方案

我很遗憾不直接回答您的问题:



恕我直言,如何在Go中实现递归锁的最佳方式是不实现它们,而是重新设计代码以使其不需要它们。我认为,很可能,对他们的渴望表明对一些(未知的)问题的错误处理正在被使用。



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



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


递归(aka可重入)互斥体是一个糟糕的主意。
使用互斥锁的根本原因是互斥锁
保护不变量,也许内部不变量,如
p.Prev.Next == p为环的所有元素,或者
外部不变量,例如我的局部变量x等于p.Prev。

锁定互斥锁声明我需要不变量保存
和也许我会暂时打破那些不变的。
释放互斥体声明我不再依赖这些
不变量和如果我打破了它们,我已经恢复了它们。



理解互斥体保护不变式对于
来说非常重要,可以确定哪里需要互斥体,哪些互斥体不需要互斥体。
例如,使用原子
增量和减量指令更新共享计数器是否需要互斥量?
它取决于不变量。如果唯一不变量是
,那么在我增加和减少
之后,计数器的值为i - d,那么指令的atmocity确保
不变量;不需要互斥。但是,如果计数器必须是
与其他一些数据结构同步(也许它计算
列表中元素的数量),那么单个操作的
的原子性是不够的。另外一点,
通常是一个互斥量,必须保护更高级别的不变量。
这就是Go上的地图上的操作不是
保证是原子操作的原因:在典型情况下,这会增加费用而不会带来
的好处。



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



  func F(){$​​ b $ b mu.Lock()
...做一些东西...
G()
...做更多的东西...
mu.Unlock()


func G(){
mu.Lock()
...做一些东西...
mu.Unlock()
}




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



递归互斥体实现会使G mu.Lock
和mu.Unlock调用在F
内调用或当前线程已经占用mu的任何其他上下文中都是no-ops。
如果mu使用这种实现,那么当mu.Lock
返回G内时,不变量可能会或可能不会成立。它取决于在调用G之前F完成的
。也许F甚至没有意识到G需要那些不变量并且已经打破它们(完全是
可能,特别是在复杂的代码中)。 / p>

递归互斥锁不保护不变量。
互斥锁只有一个工作,递归互斥锁不会这样做。



它们有一些简单的问题,比如如果你写了




  func F(){$​​ b $ b mu.Lock()
...做一些




你永远无法找到错误单线程测试。
但这只是一个更大问题的特殊情况,
就是说它们根本不提供任何保证,大约
是互斥体保护的不变量。



如果您需要实现可以使用或不使用互斥锁的可以称为
的功能,则最简单的做
的就是编写两个版本。例如,您可以写下:



  / /被称为已经举行了亩。 
//呼叫者必须小心以确保...
func g(){
...做某些事情...
}

)func G(){
mu.Lock()
g()
mu.Unlock()
}




或者它们都是未导出的,g和gLocked。



我是确定我们最终需要TryLock;随意
给我们一个CL。超时锁定似乎不是必需的
,但如果有一个干净的实现(我不知道一个)
,那么也许它会没事的。请不要发送一个CL
实现递归互斥。



递归互斥只是一个错误,只不过是
a而已。


Russ



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天全站免登陆