Go sync
包中有Mutex
。不幸的是,它不是递归的。在Go中实现递归锁的最佳方法是什么?
答案 0 :(得分:26)
我很抱歉没有直接回答你的问题:
恕我直言,如何在Go中实现递归锁定的最佳方法是不实现它们,而是重新设计代码以便首先不需要它们。我认为,对他们的渴望很可能表明对某些(这里未知)问题采用了错误的方法。
作为上述声明的间接“证明”:递归锁是否是涉及互斥锁的常见情况的常见/正确方法,它迟早会包含在标准库中。
最后,最后但并非最不重要:来自Go开发团队的Russ Cox在这里写了https://groups.google.com/d/msg/golang-nuts/XqW1qcuZgKg/Ui3nQkeLV80J:
递归(又名可重入)互斥体是一个坏主意。 使用互斥锁的根本原因是互斥锁 保护不变量,也许是内部不变量 “p.Prev.Next == p for the ring of the ring”,或者也许 外部不变量,如“我的局部变量x等于p.Prev。”
锁定互斥锁断言“我需要保持不变量” 也许“我会暂时打破这些不变量。” 释放互斥锁断言“我不再依赖那些 不变量“和”如果我打破它们,我已经恢复了它们。“
了解互斥锁保护不变量至关重要 确定需要互斥锁的位置以及不需要互斥锁的位置。 例如,共享计数器是否使用atomic更新 增量和减量指令需要一个互斥量? 这取决于不变量。如果唯一的不变量就是那个 在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。
递归互斥实现会使G的mu.Lock 当从F内调用时,mu.Unlock调用为no-ops 或当前线程已经拥有mu的任何其他上下文。 如果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 实现递归互斥。
递归互斥体只是一个错误,仅此而已 一个舒适的家园。
拉斯
答案 1 :(得分:0)
您可以轻松地从sync.Mutex和sync.Cond中进行递归锁定。有关一些想法,请参阅Appendix A here。
除了,因为Go运行时没有公开goroutine Id的任何概念。这是为了阻止人们使用goroutine本地存储进行愚蠢的事情,并且可能表明设计人员认为如果你需要一个goroutine Id你做错了。
如果你真的想,你当然可以dig the goroutine Id out of the runtime with a bit of C。你可能想要阅读那个帖子,看看为什么Go的设计师认为这是一个坏主意。
答案 2 :(得分:-4)
就像已经建立的那样,从并发的角度来看,这是一个悲惨,可怕,可怕和可怕的想法。
无论如何,既然你的问题实际上是关于Go的类型系统,那么你将如何用递归方法定义一个类型。
type Foo struct{}
func (f Foo) Bar() { fmt.Println("bar") }
type FooChain struct {
Foo
child *FooChain
}
func (f FooChain) Bar() {
if f.child != nil {
f.child.Bar()
}
f.Foo.Bar()
}
func main() {
fmt.Println("no children")
f := new(FooChain)
f.Bar()
for i := 0; i < 10; i++ {
f = &FooChain{Foo{}, f}
}
fmt.Println("with children")
f.Bar()
}