Go中的递归锁定

Go中的递归锁定,go,Go,Go的sync软件包有一个互斥锁。不幸的是,它不是递归的。在Go中实现递归锁的最佳方法是什么?很抱歉,我没有直接回答您的问题: IMHO,在Go中实现递归锁的最佳方法是不实现它们,而是重新设计代码,使其在一开始就不需要它们。我认为,对它们的渴望很可能表明对某些(此处未知)问题采用了错误的方法 作为上述声明的间接“证明”:递归锁是否是处理涉及互斥锁的常见/正确方法,它迟早会包含在标准库中 最后,最后但并非最不重要的一点:Go开发团队的Russ Cox在这里写道: 递归(也称为可重入)互斥是个坏主意

Go的
sync
软件包有一个
互斥锁
。不幸的是,它不是递归的。在Go中实现递归锁的最佳方法是什么?

很抱歉,我没有直接回答您的问题:

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

作为上述声明的间接“证明”:递归锁是否是处理涉及互斥锁的常见/正确方法,它迟早会包含在标准库中

最后,最后但并非最不重要的一点:Go开发团队的Russ Cox在这里写道:

递归(也称为可重入)互斥是个坏主意。 使用互斥体的根本原因是互斥体 保护不变量,可能是内部不变量,比如 “p.Prev.Next==p表示环的所有元素”,或者 外部不变量,如“我的局部变量x等于p.Prev”

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

理解互斥体保护不变量对于 确定哪里需要互斥锁,哪里不需要互斥锁。 例如,是否使用原子更新共享计数器 递增和递减指令需要互斥锁吗? 这取决于不变量。如果唯一不变的是 计数器在i递增和d递减后具有值i-d, 然后,说明的灵活性确保了 不变量;不需要互斥。但是,如果必须使用计数器 与其他一些数据结构同步(可能很重要 列表中的元素数),然后是 单个操作是不够的。还有一件事, 通常是互斥,必须保护较高级别的不变量。 这就是Go中地图操作不可用的原因 保证是原子的:它会增加费用,而不会 在典型案例中受益

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

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

递归互斥实现将使G的mu.Lock 和mu.Unlock调用在F内调用时是无操作的 或者当前线程已经持有mu的任何其他上下文。 如果mu使用了这样的实现,那么当mu.Lock 返回G中的不变量,不变量可能成立,也可能不成立。视情况而定 F在打电话给G之前做了些什么也许F根本没有意识到 G需要这些不变量,并且(完全)破坏了它们 可能的,尤其是在复杂代码中)

递归互斥体不保护不变量。 互斥体只有一个任务,而递归互斥体则没有

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

你永远不会在单线程测试中发现这个bug。 但这只是更大问题的一个特例, 也就是说他们根本不保证 互斥体要保护的不变量

如果您需要实现可调用的功能 无论是否持有互斥,最清晰的做法 就是写两个版本。例如,不是上面的G, 你可以写:

或者如果它们都没有被报告,g和gLocked

我相信我们最终会需要特里洛克;随便 给我们寄一张信用证。带超时的锁似乎不那么重要 但是如果有一个干净的实现(我不知道有) 那也许没关系。请不要发送这样的CL 实现递归互斥

递归互斥只是一个错误,只不过 虫子舒适的家

罗斯


您可以很容易地对和进行递归锁定。请参阅以获取一些想法

除了Go运行时没有公开goroutine Id的任何概念。这是为了阻止人们使用goroutine本地存储做愚蠢的事情,并且可能表明设计者认为如果你需要goroutine Id,你就错了


如果你真的想,当然可以。你可能想读一下这篇文章,看看为什么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()
}
type Foo struct{}
func(f Foo)Bar(){fmt.Println(“Bar”)}
类型FooChain结构{
福
儿童食物链
}
func(f FooChain)Bar(){
如果f.child!=nil{
f、 child.Bar()
}
f、 Foo.Bar()
}
func main(){
fmt.Println(“无子女”)
f:=新(食物链)
f、 Bar()
对于i:=0;i<10;i++{
f=&FooChain{Foo{},f}
}
fmt.Println(“有子女”)
f、 Bar()
}

。他不喜欢他们!我创建了一个允许计数/递归锁的库。试一试:github.com/jwells131313/goetheI猜想所有支持递归锁的语言(其中大多数)都是糟糕的、可怕的、可怕的。不一定,只是在Go中管理并发性很糟糕。您试图解决的问题是什么?@ErikAigner锁通常比其他并行设计构造慢。另一种模式(当需要提供同步时)可能是使用Go通道。你的“证据”回避了一个问题:Erik有一个递归锁定可能解决的问题,但它在标准库中还不可用。我们不需要添加支持,因为如果需要,它会
     func F() {
             mu.Lock()
             ... do some stuff
     }
     // 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()
     }
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()
}