map はデフォルトで並行安全ではなく、map に対して並行して読み書きを行うと、プログラムは panic します。その理由は以下の通りです:
Go の公式は長時間の議論の末、Go の map は典型的な使用シーン(複数の goroutine から安全にアクセスする必要がない)に適応すべきであり、一部のケース(並行アクセス)のために大多数のプログラムがロックコスト(パフォーマンス)を負うことを決定し、サポートしないことにしました。
シーン:2 つのゴルーチンが同時に読み書きすると、以下のプログラムで致命的なエラーが発生します:fatal error: concurrent map writes
package main
import (
"fmt"
"time"
)
func main() {
s := make(map[int]int)
for i := 0; i < 100; i++ {
go func(i int) {
s[i] = i
}(i)
}
for i := 0; i < 100; i++ {
go func(i int) {
fmt.Printf("map第%d个元素值是%d\n", i, s[i])
}(i)
}
time.Sleep(1 * time.Second)
}
map をスレッド安全に実現するには、2 つの方法があります:
方法一:読み書きロックを使用する map + sync.RWMutex
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var lock sync.RWMutex
s := make(map[int]int)
for i := 0; i < 100; i++ {
go func(i int) {
lock.Lock()
s[i] = i
lock.Unlock()
}(i)
}
for i := 0; i < 100; i++ {
go func(i int) {
lock.RLock()
fmt.Printf("map第%d个元素值是%d\n", i, s[i])
lock.RUnlock()
}(i)
}
time.Sleep(1 * time.Second)
}
方法二:Go が提供する sync.Map を使用する
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var m sync.Map
for i := 0; i < 100; i++ {
go func(i int) {
m.Store(i, i)
}(i)
}
for i := 0; i < 100; i++ {
go func(i int) {
v, ok := m.Load(i)
fmt.Printf("Load: %v, %v\n", v, ok)
}(i)
}
time.Sleep(1 * time.Second)
}