概要
少し前に以下のメモをアップしました。
上記ではスライスでしたが、マップも同様です。
こちらはスライスとは違って、ちょっとでも非同期アクセスすると即落ちるのであまり問題になることは無いと思います。
マップに関しては 公式のFAQ でも言及されています。
https://go.dev/doc/faq#atomic_maps
以下、一応メモです。
サンプル
// マップ操作 はスレッドセーフでは無いというのを示すサンプルです。 // 本サンプルはデータ競合が発生しています。 // // Go本家のFAQにもmap操作はatomicでは無いですよと記載がある。 // - https://go.dev/doc/faq#atomic_maps // // REFERENCES: // - https://go.dev/doc/faq#atomic_maps // - https://stackoverflow.com/questions/44152988/append-not-thread-safe // - https://stackoverflow.com/questions/49879322/can-i-concurrently-write-different-slice-elements package main import ( "flag" "fmt" "strconv" "sync" ) func main() { var ( verbose = flag.Bool("verbose", false, "verbose output") ) flag.Parse() var ( src = make(map[string]bool) dst = make(map[string]bool) ) for i := 0; i < 5; i++ { src[strconv.Itoa(i)] = true } wg := sync.WaitGroup{} for k, v := range src { wg.Add(1) go func(k string, v bool) { defer wg.Done() dst[k] = v }(k, v) } wg.Wait() fmt.Printf("src-len=%d\tdst-len=%d\n", len(src), len(dst)) if *verbose { fmt.Println("=========== SRC ===========") for _, v := range src { fmt.Println(v) } fmt.Println("=========== DST ===========") for _, v := range dst { fmt.Println(v) } } }
Taskfile.yml
version: '3' tasks: fmt: cmds: - go fmt ./... vet: cmds: - go vet ./... run: cmds: - cmd: for i in {1..10} ; do go run race/main.go; done silent: true run-notrace: cmds: - cmd: for i in {1..10} ; do go run -race notrace/main.go; done silent: true run-with-raceoption: cmds: - cmd: go run -race race/main.go ignore_error: true
実行すると以下のようになります。
gitpod /workspace/try-golang (master) $ task -d examples/singleapp/map_is_not_threadsafe/ run fatal error: concurrent map writes goroutine 6 [running]: runtime.throw({0x4a3255?, 0x0?}) /home/gitpod/go/src/runtime/panic.go:992 +0x71 fp=0xc00005a700 sp=0xc00005a6d0 pc=0x430a71 runtime.mapassign_faststr(0x0?, 0x0?, {0x4a6178, 0x1}) /home/gitpod/go/src/runtime/map_faststr.go:212 +0x39c fp=0xc00005a768 sp=0xc00005a700 pc=0x410c3c main.main.func1({0x4a6178?, 0x0?}, 0x1) /workspace/try-golang/examples/singleapp/map_is_not_threadsafe/race/main.go:41 +0x78 fp=0xc00005a7b8 sp=0xc00005a768 pc=0x4878b8 main.main.func2() /workspace/try-golang/examples/singleapp/map_is_not_threadsafe/race/main.go:42 +0x32 fp=0xc00005a7e0 sp=0xc00005a7b8 pc=0x487812 runtime.goexit() /home/gitpod/go/src/runtime/asm_amd64.s:1571 +0x1 fp=0xc00005a7e8 sp=0xc00005a7e0 pc=0x45bb01 created by main.main /workspace/try-golang/examples/singleapp/map_is_not_threadsafe/race/main.go:38 +0x1be goroutine 1 [semacquire]: sync.runtime_Semacquire(0xc000108270?) /home/gitpod/go/src/runtime/sema.go:56 +0x25 sync.(*WaitGroup).Wait(0xc000104e78?) /home/gitpod/go/src/sync/waitgroup.go:136 +0x52 main.main() /workspace/try-golang/examples/singleapp/map_is_not_threadsafe/race/main.go:45 +0x2d4 exit status 2 task: Failed to run task "run": exit status 1
Mutexでクリティカルセクションを作っているサンプル
// マップ操作 はスレッドセーフでは無いというのを示すサンプルです。 // 本サンプルはデータ競合が発生しません。 // // REFERENCES: // - https://stackoverflow.com/questions/44152988/append-not-thread-safe // - https://stackoverflow.com/questions/49879322/can-i-concurrently-write-different-slice-elements package main import ( "flag" "fmt" "strconv" "sync" ) func main() { var ( verbose = flag.Bool("verbose", false, "verbose output") ) flag.Parse() var ( mu sync.Mutex src = make(map[string]bool) dst = make(map[string]bool) ) for i := 0; i < 5; i++ { src[strconv.Itoa(i)] = true } wg := sync.WaitGroup{} for k, v := range src { wg.Add(1) go func(k string, v bool) { defer wg.Done() mu.Lock() dst[k] = v mu.Unlock() }(k, v) } wg.Wait() fmt.Printf("src-len=%d\tdst-len=%d\n", len(src), len(dst)) if *verbose { fmt.Println("=========== SRC ===========") for _, v := range src { fmt.Println(v) } fmt.Println("=========== DST ===========") for _, v := range dst { fmt.Println(v) } } }
実行すると以下のようになります。
gitpod /workspace/try-golang (master) $ task -d examples/singleapp/map_is_not_threadsafe/ run-notrace src-len=5 dst-len=5 src-len=5 dst-len=5 src-len=5 dst-len=5 src-len=5 dst-len=5 src-len=5 dst-len=5 src-len=5 dst-len=5 src-len=5 dst-len=5 src-len=5 dst-len=5 src-len=5 dst-len=5 src-len=5 dst-len=5
まあ、Goで非同期処理する場合は チャネル っていうとても素晴らしい機構があるのでそれを使うべしってことですね。
参考情報
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。