【发布时间】:2013-01-18 05:20:01
【问题描述】:
Go 的 sync 包有一个 Mutex。不幸的是它不是递归的。在 Go 中实现递归锁的最佳方式是什么?
【问题讨论】:
-
我创建了一个允许计数/递归锁的库。试试看:github.com/jwells131313/goethe
标签: go
Go 的 sync 包有一个 Mutex。不幸的是它不是递归的。在 Go 中实现递归锁的最佳方式是什么?
【问题讨论】:
标签: go
很抱歉没有直接回答你的问题:
恕我直言,如何在 Go 中实现递归锁的最佳方法是不实现它们,而是重新设计你的代码,使其一开始就不需要它们。我认为,对他们的渴望很可能表明正在使用错误的方法来解决某些(此处未知)问题。
作为上述主张的间接“证明”:对于涉及互斥锁的/某些常见情况,递归锁是否是一种常见/正确的方法,它迟早会包含在标准库中。
最后,最后但并非最不重要的一点:Go 开发团队的 Russ Cox 在这里写的 https://groups.google.com/d/msg/golang-nuts/XqW1qcuZgKg/Ui3nQkeLV80J:
递归(又名可重入)互斥锁是个坏主意。 使用互斥锁的根本原因是互斥锁 保护不变量,也许是内部不变量,例如 “p.Prev.Next == p for all elements of the ring”,或者也许 外部不变量,例如“我的局部变量 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。
递归互斥锁实现将使 G 的 mu.Lock 并且 mu.Unlock 调用在 F 内调用时是无操作的 或当前线程已经拥有 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 实现递归互斥锁。
递归互斥体只是一个错误,无非就是 虫子的舒适之家。
罗斯
【讨论】:
func F(callback func()),它锁定一个互斥体,然后调用回调。当然callback是不允许再拨打F的。但如果用户不小心这样做呢?僵局。很难发现,如果其他 go 例程仍在运行。可以使用递归互斥锁轻松检测这种情况,然后改为恐慌(Go 的互斥锁不会这样做),立即使问题对用户显而易见。
您可以很容易地从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 的设计者认为这是一个坏主意。
【讨论】:
正如已经确定的那样,从并发的角度来看,这是一个悲惨、可怕、可怕和可怕的想法。
无论如何,既然您的问题实际上是关于 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()
}
【讨论】: