Recursive critical sections in Go

I understand that Go has no support for recursive mutexes (and is considered by many to be dangerous), and these pipes are the preferred way to implement complex concurrency patterns.

However, I cannot figure out a sane way to implement the very common concurrency pattern - retractive or recursive critical section.

Roughly: goroutines A and B will compete for a lock on a critical sector (say, some state in the structure needs to be atomically modified). Let it be said that A gets a lock. However, A recurses, you may need to enter the critical section many times. When it exits the critical section as it is entered, goroutine B will acquire the lock, etc.

I want to implement this with pipes (or any way in Go), without having to pass some string or token back and forth throughout the function call tree that can traverse the critical section (no "goroutine id"), and without the need to have a complex / expensive stack introspection using package runtime

.

How can I do that?

+3


source to share


1 answer


Let's say your example above looks like this:

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 the implementation in pipes should follow a simple principle - there is only one place where F.Value is read or written, wrapped by a select statement. Something like that:



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 are using a bi-directional buffer channel to get / set F.Value. One reader, one author, chooses all the magic to handle access.

You may also be interested in a related topic on golang nuts on re-mutexes: https://groups.google.com/forum/#!topic/golang-nuts/vN5ncBdtkcA There is a good explanation why reentry mutexes are not useful in Go (and it's not a matter of danger).

+1


source







All Articles