someuser1
someuser1

Reputation: 141

When to use mutexes

Are they really needed in Go? I've read https://gobyexample.com/mutexes and run example code multiple times, when I remove mutex.Lock() and mutex.Unlock() it works exactly the same.

Upvotes: 0

Views: 864

Answers (3)

peterSO
peterSO

Reputation: 166765

I've read https://gobyexample.com/mutexes and run example code multiple times, when I remove mutex.Lock() and mutex.Unlock() it works exactly the same.


As expected, when I remove the mutex, I get data races and panics.


Output:

$ go run -race racer.go

fatal error: concurrent map read and map write

==================
WARNING: DATA RACE
Write at 0x00c00009a690 by goroutine 113:
  runtime.mapassign_fast64()
      /home/peter/go/src/runtime/map_fast64.go:92 +0x0
  main.main.func2()
      /home/peter/gopath/src/so/racer.go:56 +0xba

Previous read at 0x00c00009a690 by goroutine 64:
  runtime.mapaccess1_fast64()
      /home/peter/go/src/runtime/map_fast64.go:12 +0x0
  main.main.func1()
      /home/peter/gopath/src/so/racer.go:37 +0x72

Goroutine 113 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:51 +0x124

Goroutine 64 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:29 +0xe0
==================
==================
WARNING: DATA RACE
Write at 0x00c00009a690 by goroutine 115:
  runtime.mapassign_fast64()
      /home/peter/go/src/runtime/map_fast64.go:92 +0x0
  main.main.func2()
      /home/peter/gopath/src/so/racer.go:56 +0xba

Previous read at 0x00c00009a690 by goroutine 39:
  runtime.mapaccess1_fast64()
      /home/peter/go/src/runtime/map_fast64.go:12 +0x0
  main.main.func1()
      /home/peter/gopath/src/so/racer.go:37 +0x72

Goroutine 115 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:51 +0x124

Goroutine 39 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:29 +0xe0
==================
==================
WARNING: DATA RACE
Read at 0x00c0001f0048 by goroutine 79:
  main.main.func1()
      /home/peter/gopath/src/so/racer.go:37 +0x80

Previous write at 0x00c0001f0048 by goroutine 113:
  main.main.func2()
      /home/peter/gopath/src/so/racer.go:56 +0xcf

Goroutine 79 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:29 +0xe0

Goroutine 113 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:51 +0x124
==================
==================
WARNING: DATA RACE
Read at 0x00c0001f0050 by goroutine 81:
  main.main.func1()
      /home/peter/gopath/src/so/racer.go:37 +0x80

Previous write at 0x00c0001f0050 by goroutine 115:
  main.main.func2()
      /home/peter/gopath/src/so/racer.go:56 +0xcf

Goroutine 81 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:29 +0xe0

Goroutine 115 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:51 +0x124
==================
==================
WARNING: DATA RACE
Read at 0x00c0001f0050 by goroutine 106:
  main.main.func1()
      /home/peter/gopath/src/so/racer.go:37 +0x80

Previous write at 0x00c0001f0050 by goroutine 115:
  main.main.func2()
      /home/peter/gopath/src/so/racer.go:56 +0xcf

Goroutine 106 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:29 +0xe0

Goroutine 115 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:51 +0x124
==================
==================
WARNING: DATA RACE
Read at 0x00c0001f0050 by goroutine 94:
  main.main.func1()
      /home/peter/gopath/src/so/racer.go:37 +0x80

Previous write at 0x00c0001f0050 by goroutine 115:
  main.main.func2()
      /home/peter/gopath/src/so/racer.go:56 +0xcf

Goroutine 94 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:29 +0xe0

Goroutine 115 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:51 +0x124
==================
==================
WARNING: DATA RACE
Write at 0x00c0001f0050 by goroutine 114:
  main.main.func2()
      /home/peter/gopath/src/so/racer.go:56 +0xcf

Previous write at 0x00c0001f0050 by goroutine 115:
  main.main.func2()
      /home/peter/gopath/src/so/racer.go:56 +0xcf

Goroutine 114 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:51 +0x124

Goroutine 115 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:51 +0x124
==================
exit status 2
$ 

racer.go:

package main

import (
    "fmt"
    "math/rand"

    //*"sync"
    "sync/atomic"
    "time"
)

func main() {

    // For our example the state will be a map.

    var state = make(map[int]int)

    // This mutex will synchronize access to state.

    //*var mutex = &sync.Mutex{}

    // We’ll keep track of how many read and write operations we do.

    var readOps uint64
    var writeOps uint64

    // Here we start 100 goroutines to execute repeated reads against the state, once per millisecond in each goroutine.

    for r := 0; r < 100; r++ {
        go func() {
            total := 0
            for {

                // For each read we pick a key to access, Lock() the mutex to ensure exclusive access to the state, read the value at the chosen key, Unlock() the mutex, and increment the readOps count.

                key := rand.Intn(5)
                //*mutex.Lock()
                total += state[key]
                //*mutex.Unlock()
                atomic.AddUint64(&readOps, 1)

                // Wait a bit between reads.

                time.Sleep(time.Millisecond)
            }
        }()
    }

    // We’ll also start 10 goroutines to simulate writes, using the same pattern we did for reads.

    for w := 0; w < 10; w++ {
        go func() {
            for {
                key := rand.Intn(5)
                val := rand.Intn(100)
                //*mutex.Lock()
                state[key] = val
                //*mutex.Unlock()
                atomic.AddUint64(&writeOps, 1)
                time.Sleep(time.Millisecond)
            }
        }()
    }

    // Let the 10 goroutines work on the state and mutex for a second.

    time.Sleep(time.Second)

    // Take and report final operation counts.

    readOpsFinal := atomic.LoadUint64(&readOps)
    fmt.Println("readOps:", readOpsFinal)
    writeOpsFinal := atomic.LoadUint64(&writeOps)
    fmt.Println("writeOps:", writeOpsFinal)

    // With a final lock of state, show how it ended up.

    //*mutex.Lock()
    fmt.Println("state:", state)
    //*mutex.Unlock()
}

Upvotes: 3

areller
areller

Reputation: 5238

You can't access map from several go routines at once.
It might work for some time but is bound to fail, or cause unexpected results.

Mutex guaranties that only a single go routine can operate on the piece of code between Lock and Unlock at once.

Alternatively, you can use sync.Map which is thread safe for reads and writes.

m := new(sync.Map)

go func() {
    for r := 0; true; r++ {
        m.Store(r, r)
        time.Sleep(time.Millisecond)
    }
}()

go func() {
    for r := 0; true; r++ {
        res, ok := m.Load(r)
        if ok {
            fmt.Println(res)
        }
        time.Sleep(10 * time.Millisecond)
    }
}()

sync.Map doesn't always mean thread safe

With sync.Map reads (Load) and writes (Store) are atomic, i.e. calling them from different go routines at once will work as expected/won't corrupt data or throw an error.

However, composing different sync.Map might not be atomic, and therefore not thread-safe.

For example,

val, ok := m.Load("someKey")
if !ok {
    m.Store("someKey", LoadData())
}

If this code runs from different go routines at once, there is a chance that both go routines will enter the if statement and load the data, even though it was not intended.
So sometimes you might end up needing to use mutexes instead of sync.Map

val, ok := m.Load("someKey")
if !ok {
    mutex.Lock()
    defer mutex.Unlock()
    val, ok = m.Load("someKey")
    if !ok {
        m.Store("someKey", LoadData())
    }
}

Upvotes: 0

Patrick Vollebregt
Patrick Vollebregt

Reputation: 41

Yes, the map in go is not thread safe. In this example map access COULD happen from multiple threads. When the map is written from multiple threads or written and read from multiple threads undefined behavior will occur. However, when and if this happens depends on timing. So removing the mutex will probably cause data corruption or a crash at some point in the future.

Upvotes: 0

Related Questions