0

After reading the mutex examples on golang.org and stackoverflow, I'm still not sure about the declaration and idiomatic usage with anonymous functions. Therefore I've summarized a few examples.

Are examples A, B and C nearly equivalent or are there major differences that I don't notice? I would prefer the global example "B". I guess if I'm careful with it, it's probably the simplest solution.

Or is there maybe a better approach to use mutex?

This example on go playground

package main

import (
    "fmt"
    "sync"
)

type MuContainer struct {
    sync.RWMutex
    data int
}

var mucglobal = &MuContainer{}

func main() {

    // A: Global declaration - working: adds 45
    for i := 0; i < 10; i++ {
        go func(j int, mucf *MuContainer) {
            mucf.Lock()
            mucf.data += j
            mucf.Unlock()
        }(i, mucglobal)
    }

    // B: Global only - working: adds 45
    for i := 0; i < 10; i++ {
        go func(j int) {
            mucglobal.Lock()
            mucglobal.data += j
            mucglobal.Unlock()
        }(i)
    }

    // C: Local declaration - working: adds 45
    muclocal := &MuContainer{}
    for i := 0; i < 10; i++ {
        go func(j int, mucf *MuContainer) {
            mucf.Lock()
            mucf.data += j
            mucf.Unlock()
        }(i, muclocal)
    }

    // // D: Pointer to struct - not working: adds 0
    // // I guess because it points directly to the struct.
    // for i := 0; i < 10; i++ {
    //  go func(j int, mucf *MuContainer) {
    //      mucf.Lock()
    //      mucf.data += j
    //      mucf.Unlock()
    //  }(i, &MuContainer{})
    // }

    for {
        mucglobal.RLock()
        muclocal.RLock()
        fmt.Printf("global: %d / local: %d\n", mucglobal.data, muclocal.data)
        if mucglobal.data == 90 && muclocal.data == 45 {
            muclocal.RUnlock()
            mucglobal.RUnlock()
            break
        }
        muclocal.RUnlock()
        mucglobal.RUnlock()
    }
}
Ganso
  • 53
  • 5
  • See related / possible duplicate: [When do you embed mutex in struct in Go?](https://stackoverflow.com/questions/44949467/when-do-you-embed-mutex-in-struct-in-go/44950096#44950096) – icza Jun 29 '21 at 19:41
  • There is no substantial difference between A, B and C. You are probably overrating "idiomatic" here. – Volker Jun 29 '21 at 19:55
  • A and B are identical. C may or may not work differently in real world code (depending on whether there's other code accessing the same package var), but in this code it's identical to A and B. D doesn't make any sense in this context, may or may not be valid in any other hypothetical context. – Adrian Jun 29 '21 at 19:57
  • @Volker and Adrian Are there other methods that are more different? i'm using mutex for the first time and looking for a recommended start. – Ganso Jun 29 '21 at 20:08
  • 1
    You are mixing up different things. A mutex is a mutex. Normaly you use a *sync.Mutex, not a mutex as mutexes must not be copied. Embeding a mutex is okay, but if you do not feel comfortable with mutextes, struct literals, and popinters: why not just use a simple (unembedded) field. You probably overthink this. – Volker Jun 29 '21 at 21:22
  • @Volker Thanks, you mean declaring e.g. in func main like `var mu *sync.Mutex` and than just `mu.Lock()` / `mu.Unlock()`. This would feel much better. – Ganso Jun 29 '21 at 23:10
  • 1
    Disclaimer: I have no idea what your code is trying to do or which problem you are trying to solve. Sometimes global or local variables are okay and then having a block `var ( xyzMu = new(sync.Mutex); xyz XYZ)` is fine. Mutex and variable protected by that mutex go side by side. Sometimes it's better to have the mutex as a field. If you use a field you can have a "normal" field or "embed" a mutex (your code embeds it). Empirically embedding is problematic for newcommers to the language. It really doesn't matter _how_ you do it if you do it _correctly_ . – Volker Jun 30 '21 at 05:22
  • Thank you for the example. Now I understand :-) – Ganso Jun 30 '21 at 09:02

1 Answers1

3

D is not working because you are creating a new struct for each iteration. In the end, you'll have 10 independent instances of MuContainer.

The first two options are semantically identical. The bottom line for those two is that each goroutine shares the same instance of the object, which happens to be a global var.

The second one is similar with the only difference being the object locked and updated happens to be a local var. Again, the goroutines are working on the same instance of the object.

So these are not really different from each other, and all three have their uses.

Burak Serdar
  • 46,455
  • 3
  • 40
  • 59