Go中的递归临界区

huangapple go评论78阅读模式
英文:

Recursive critical sections in Go

问题

我理解到在Go语言中没有对递归互斥锁提供支持(而且很多人认为这些锁是危险的),而且通道是实现复杂并发模式的首选方式。

然而,我无法找到任何明智的方法来实现一个非常常见的并发模式 - 可重入或递归的临界区。

大致上:goroutine A 和 B 将竞争一个关键区域的锁(比如说需要原子修改的结构体中的某个状态)。假设 A 获得了锁。然而,A 将会递归,可能需要多次进入关键区域。当它像进入时一样退出关键区域后,goroutine B 将获得锁,依此类推。

我想使用通道来实现这个(或者以其他任何方式在Go中实现),而不必通过整个函数调用树传递一些字符串或令牌(没有“goroutine id”可用),也不必使用runtime包进行混乱/昂贵的堆栈检查。

如何实现这个呢?

英文:

I understand that there is no support for recursive mutexes in Go (and that a lot of folks consider these dangerous), and that channels are the preferred way to implement complex concurrency patterns.

However, I cannot figure out any sensible way to implement a very common concurrency pattern - that of the re-entrant or recursive critical section.

Roughly: goroutines A and B will compete for a lock over a critical section (say some state in a struct needs to be atomically modified). Let's say A receives the lock. However, A will recurse, possibly needing to enter the critical section many times. When it has exited the critical section as it has entered it, goroutine B will get the lock, etc.

I want to implement this with channels (or in any way possible in Go otherwise), without having to pass some string or token back and forth through the whole call tree of functions that might pass through the critical section (there is no "goroutine id" available)., and without having messy/expensive stack introspection necessary using runtime package.

How can this be done?

答案1

得分: 1

你好,以下是翻译好的内容:

假设,你上面的示例代码如下所示:

package main

import "fmt"

type Foo struct {
    // 这里必须是可重入的互斥锁
    Value int
}

var F Foo

func A() {
    F.Value += 1
    if F.Value < 10 {
        A()
    }
}

func B() {
    F.Value += 5
    if F.Value < 20 {
        A()
    }
}

func main() {
    F = Foo{
        Value: 0,
    }
    A()
    B()
    fmt.Println("F is", F.Value)
}

然后,使用通道进行实现应遵循简单原则-只有一个地方读取或写入F.Value,由select语句包裹。类似于以下代码:

package main

import "fmt"

type Foo struct {
    Value int
}

var (
    F  Foo
    ch = make(chan int)
)

func A() {
    val := <-ch
    ch <- val + 1
    if val < 10 {
        A()
    }
}

func B() {
    val := <-ch
    ch <- val + 5
    if val < 20 {
        A()
    }
}

func main() {
    F = Foo{
        Value: 0,
    }
    go func() {
        for {
            select {
            case val := <-ch:
                F.Value = val
            case ch <- F.Value:
            }
        }
    }()
    A()
    B()
    fmt.Println("F is", F.Value)
}

在这里,我们使用双向缓冲通道来获取/设置F.Value。一个读取者,一个写入者,select语句处理所有访问的魔法。

你可能还对golang-nuts上关于可重入互斥锁的相关主题感兴趣:https://groups.google.com/forum/#!topic/golang-nuts/vN5ncBdtkcA 这里有很好的解释,为什么在Go中可重入互斥锁没有用(这不是危险的问题)。

英文:

Say, your example above looks like:

package main

import &quot;fmt&quot;

type Foo struct {
	// here must be reentrant mutex
	Value int
}

var F Foo

func A() {
	F.Value += 1
	if F.Value &lt; 10 {
		A()
	}
}

func B() {
	F.Value += 5
	if F.Value &lt; 20 {
		A()
	}
}

func main() {
	F = Foo{
		Value: 0,
	}
	A()
	B()
	fmt.Println(&quot;F is&quot;, F.Value)
}

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

Then implementation in channels should follow simple principle - only one place where F.Value is read or written, wrapped by select statement. Something like this:

package main

import &quot;fmt&quot;

type Foo struct {
	Value int
}

var (
	F  Foo
	ch = make(chan int)
)

func A() {
	val := &lt;-ch
	ch &lt;- val+1
	if val &lt; 10 {
		A()
	}
}

func B() {
	val := &lt;-ch
	ch &lt;- val+5
	if val &lt; 20 {
		A()
	}
}

func main() {
	F = Foo{
		Value: 0,
	}
	go func() {
		for {
			select {
			case val := &lt;-ch:
				F.Value = val
			case ch &lt;- F.Value:
			}
		}
	}()
	A()
	B()
	fmt.Println(&quot;F is&quot;, F.Value)
}

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

Here we use bidirectional buffered channel for getting/setting F.Value. One reader, one writer, select does all the magic to handling access.

You may also be interested in relevant topic in golang-nuts on the reentrant mutexes: https://groups.google.com/forum/#!topic/golang-nuts/vN5ncBdtkcA There are good explanation why reentrant mutexes are not useful in Go (and it's not the question of danger).

huangapple
  • 本文由 发表于 2015年5月27日 06:11:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/30469808.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定