Go中的递归关键部分

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?

Say, your example above looks like:

package main

import "fmt"

type Foo struct {
    // here must be reentrant mutex
    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)
}

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 "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)
}

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).