Go中的递归锁定

时间:2013-02-03 09:11:12

标签: go

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

3 个答案:

答案 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.Mutexsync.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()
}

http://play.golang.org/p/mPBHKpgxnd