Reputation: 141
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
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
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
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