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