「無害」なdata raceについて

## 概要

Goの「無害」なdata raceについてはたしてそれは実際には無害なのか、避けるべきなのか例を交えてまとめている

## 結論

「無害」なdata raceも避けるべき

## そもそもdata raceとは？

二つ以上のスレッドが同じ変数（メモリ）に対して同時にアクセスし、少なくとも一つのスレッドが書き込みの場合に発生する状態のこと

参考：https://ja.wikipedia.org/wiki/%E3%83%87%E3%83%BC%E3%82%BF%E7%AB%B6%E5%90%88

### data raceの例

典型的な例で言うと、以下のように二つのgroutineからcount変数に対してアクセスする場合、

以下は0,1,2,3,4,5,,と表示されるわけではない

おそらく（タイミングによって変わる）0,0,0,0,1,,1,1,1,1みたいに表示される

```go
package main

import (
	"fmt"
)

func main() {

	var count int
	ch := make(chan bool)

	go func() {
		for {
			count = count + 1
			fmt.Println(count)
		}
	}()

	go func() {
		for {
			fmt.Println(count)
		}
	}()

	<-ch

}
```

output
```shell
0
0
1
1
1
1
1
1
1
```

https://go.dev/play/p/Im2Ov8DynZ1

*count変数を正確にひとつずつインクリメントしたい場合にはこの挙動はバグである*

*これが典型的な有害なdata raceである*

これを解決するには、Mutex等を使ってcount変数へのアクセスを一つにlockする必要がある（ここでは解説しない）

参考
https://go.dev/tour/concurrency/9


## 無害なdata raceとは

では、無害なdata raceとはなんなのか？

*countの値が100%順番を保っていないでもOKという時に、それは「無害」なdata raceという*

先ほどと同じコード例

```go
package main

import (
	"fmt"
)

func main() {

	var count int
	ch := make(chan bool)

	go func() {
		for {
			count = count + 1
			fmt.Println(count)
		}
	}()

	go func() {
		for {

			fmt.Println(count)
		}
	}()

	<-ch

}
```

では、「無害」なdata raceは放っておいても良いのか？

それとも避けるべきなのか？

:::note info

Goでは、hashmapのdata raceを避けるために、data raceが検出されるとデフォルトでパニックを行うようになっている

> 文字列やHashMapのような複雑なデータ構造に対するdata raceは言うまでもなく、危険である（プログラムがクラッシュする可能性がある）>
> 

> GoではHashMapに対するdata raceは実行時にパニックを起こすようになっている
> 

↓これを実行すると

```go
package main

import "fmt"

func main() {
	ch := make(chan bool)
	hashMap := map[string]string{"a": "a"}

	go func() {
		for {
			hashMap["a"] = "aa"
		}
	}()

	go func() {
		for {
			fmt.Println(hashMap["a"])
		}

	}()

	<-ch

}
```

↓のようにパニックを引き起こす

```bash
fatal error: concurrent map read and map write

goroutine 7 [running]:
main.main.func2()
	/tmp/sandbox504299743/prog.go:19 +0x35
created by main.main in goroutine 1
	/tmp/sandbox504299743/prog.go:17 +0xf6

goroutine 1 [chan receive]:
main.main()
	/tmp/sandbox504299743/prog.go:24 +0x105

goroutine 6 [runnable]:
main.main.func1()
	/tmp/sandbox504299743/prog.go:13 +0x45
created by main.main in goroutine 1
	/tmp/sandbox504299743/prog.go:11 +0xb6

Program exited.
```

:::


## 改めて結論

**「無害」なdata raceも避けるべき**

**参考：Benign Data Races: What Could Possibly Go Wrong?**

Goのrace detector作者による「無害な」data raceは何を引き起こすのかをまとめた資料

https://docs.google.com/document/d/1WAT22FtFdMt43i3O8YrnlQTNTzZ3IoJBtu3P2DNRytg/edit

## なぜなら

著者は以下の理由を挙げている

```go
var count int

count++
```

1. Goにおける↑のようなコードは未定義の動作である
    未定義の動作とは実行時に事実上どんな動作でも起こり得ると言うことを指している

2. atomicやmutexを利用することで、data raceが発生しているのかどうかがコードで明確になる

3. このような無害なdata raceを残しておくことで、data race detactorが検知する他の重要なdata raceを見逃してしまう可能性がある

## 「無害」なdata raceがどのようにして悪影響を及ぼす可能性があるのか例

前項の未定義の動作とは、どのような動作が考え得るのか

↓この場合、goroutine1でstop=trueになる前に、goroutine2が終了する可能性がある

```go
var stop bool;

// goroutine1
go func(){
  ...
	stop = true
}()

// goroutine2
go func(){
	for {
		if stop {
			return
		}
		...
	}
}()
```

コンパイラによっては、race freeである前提で動作をする

変数stopが定義されるとそのメモリ領域を一時的なデータを格納しておく利用をする場合がある

その際に、stopに予期せぬデータが入っている状態でgoroutine2がstop変数にアクセスすると、予期せぬタイミングでgoroutine2が終了する

また、たとえば一時的なデータが非常に危険な関数のポインタとすると、予期せぬタイミングでその関数が実行されることになり、非常に危険な状態！

## Go公式ドキュメントにも記載

Goの公式ドキュメントを読むと↓以下のモットーがある

> Do not communicate by sharing memory; instead, share memory by communicating.
> 

メモリをシェアすることでgoroutine間でコミュニケーションするのではなく、コミュニケーションをすることでメモリをシェアしよう！


## 参考
- https://docs.google.com/document/d/1WAT22FtFdMt43i3O8YrnlQTNTzZ3IoJBtu3P2DNRytg/edit
- https://go.dev/ref/mem
- https://go.dev/doc/effective_go#concurrency
- https://groups.google.com/g/golang-nuts/c/ai0eQtnas7A
- https://www.reddit.com/r/golang/comments/yb1ijr/race_or_not_race/