go.uber.org/atomic パッケージを使って、atomicな値を扱う。

## tr:dr
- Go routineを使って並行処理する中で同じ値を扱うときには`sync/atomic`パッケージなどを使い、atomicな処理にする必要がある。
- Uberが `sync/atomic`のwrapperを作っていて便利そうだったので、試してみた。
   - https://godoc.org/go.uber.org/atomic 
  
## Go routineを使ったカウントアップ
- Go routineを使ったカウントアップのプログラムを書いてみます。syncパッケージを使って1000回のcountupを行うプログラムです。

```golang

package singlecpu

import (
	"sync"
)

func count() int64 {

	var count int64
	var wg sync.WaitGroup
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			count++
			wg.Done()
		}()
	}
	wg.Wait()
	return count
}
```
- テストコードを書いて実行してみます。

```golang
package singlecpu

import (
	"fmt"
	"runtime"
	"testing"
)

func TestCount(t *testing.T) {
	tests := []struct {
		expect     int64
	}{
		{
			expect:     1000,
		},
	}
	for _, test := range tests {
		t.Run(fmt.Sprintf("expect: %d", test.expect), func(t *testing.T) {
			actual := count()
			if actual != test.expect {
				t.Errorf("Assert error failed actual: %d expext: %d GOMAXPROCS: %d", actual, test.expect, runtime.GOMAXPROCS(0))
			}
		})
	}

}

```

```console
go test ./...
ok  	github.com/yutachaos/go-practice/atomic	0.205s
```
- 大丈夫ですね。

#### GOMAXPROCS
- GOではGOMAXPROCSで同時に利用するCPUの変更をすることが出来ます。今度はこちらを変更しつつtestを実行してみます。
   - テストコード内の `runtime.GOMAXPROCS(test.GOMAXPROCS)` を使ってプログラム内で変更しつつ実行。

```golang
package multicpu

import (
	"fmt"
	"runtime"
	"testing"
)

func TestCount(t *testing.T) {
	tests := []struct {
		GOMAXPROCS int
		expect     int64
	}{
		{
			GOMAXPROCS: 1,
			expect:     1000,
		},
		{
			GOMAXPROCS: 2,
			expect:     1000,
		},
		{
			GOMAXPROCS: 3,
			expect:     1000,
		},
		{
			GOMAXPROCS: 4,
			expect:     1000,
		},
	}
	for _, test := range tests {
		t.Run(fmt.Sprintf("GOMAXPROCS: %d", test.GOMAXPROCS), func(t *testing.T) {
			runtime.GOMAXPROCS(test.GOMAXPROCS)
			actual := count()
			if actual != test.expect {
				t.Errorf("Assert error failed actual: %d expext: %d GOMAXPROCS: %d", actual, test.expect, runtime.GOMAXPROCS(0))
			}
		})
	}

}
```

- 実行結果

```console
go test ./...
--- FAIL: TestCount (0.00s)
    --- FAIL: TestCount/GOMAXPROCS:_2 (0.00s)
        main_test.go:36: Assert error failed actual: 965 expext: 1000 GOMAXPROCS: 2
    --- FAIL: TestCount/GOMAXPROCS:_3 (0.00s)
        main_test.go:36: Assert error failed actual: 865 expext: 1000 GOMAXPROCS: 3
    --- FAIL: TestCount/GOMAXPROCS:_4 (0.00s)
        main_test.go:36: Assert error failed actual: 889 expext: 1000 GOMAXPROCS: 4
FAIL
FAIL	github.com/yutachaos/go-practice/atomic/multicpu	0.077s
FAIL
```
- CPUが1個のときは大丈夫ですが、2個以上のときに失敗しました。
- 細かい説明はここでは省略しますが、countの演算がatomicに行われないためにこのような事象が発生します。

### `-race` オプション
- `go test`に`-race` オプションを付けて実行することで、このようなrace conditionが発生する可能性があるコードに警告を出すことが出来ます。

```console

go test ./... -race
==================
WARNING: DATA RACE
Read at 0x00c00001a188 by goroutine 11:
  github.com/yutachaos/go-practice/atomic/multicpu_invalid.count.func1()
      /Users/yutachaos/src/github.com/yutachaos/go-practice/atomic/multicpu_invalid/count.go:14 +0x38

Previous write at 0x00c00001a188 by goroutine 150:
  github.com/yutachaos/go-practice/atomic/multicpu_invalid.count.func1()
      /Users/yutachaos/src/github.com/yutachaos/go-practice/atomic/multicpu_invalid/count.go:14 +0x4e

Goroutine 11 (running) created at:
  github.com/yutachaos/go-practice/atomic/multicpu_invalid.count()
      /Users/yutachaos/src/github.com/yutachaos/go-practice/atomic/multicpu_invalid/count.go:13 +0xe4
  github.com/yutachaos/go-practice/atomic/multicpu_invalid.TestCount.func1()
      /Users/yutachaos/src/github.com/yutachaos/go-practice/atomic/multicpu_invalid/count_test.go:34 +0x71
  testing.tRunner()
      /usr/local/Cellar/go/1.13.1/libexec/src/testing/testing.go:909 +0x199

Goroutine 150 (finished) created at:
  github.com/yutachaos/go-practice/atomic/multicpu_invalid.count()
      /Users/yutachaos/src/github.com/yutachaos/go-practice/atomic/multicpu_invalid/count.go:13 +0xe4
  github.com/yutachaos/go-practice/atomic/multicpu_invalid.TestCount.func1()
      /Users/yutachaos/src/github.com/yutachaos/go-practice/atomic/multicpu_invalid/count_test.go:34 +0x71
  testing.tRunner()
      /usr/local/Cellar/go/1.13.1/libexec/src/testing/testing.go:909 +0x199
==================
--- FAIL: TestCount (0.24s)
    --- FAIL: TestCount/GOMAXPROCS:_1 (0.10s)
        testing.go:853: race detected during execution of test
    --- FAIL: TestCount/GOMAXPROCS:_3 (0.05s)
        count_test.go:36: Assert error failed actual: 999 expext: 1000 GOMAXPROCS: 3
    --- FAIL: TestCount/GOMAXPROCS:_4 (0.05s)
        count_test.go:36: Assert error failed actual: 998 expext: 1000 GOMAXPROCS: 4
    testing.go:853: race detected during execution of test
FAIL
FAIL	github.com/yutachaos/go-practice/atomic/multicpu_invalid	0.505s
FAIL
```

## sync/atomic
- `sync/atomic`を利用することで、Golangで複数のgo routineから変数がアクセスされる際にatomicな計算を提供することができます。

```golang
package multicpu_invalid

import (
	"sync"
	"sync/atomic"
)

func count() int64 {

	var count int64
	var wg sync.WaitGroup
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			atomic.AddInt64(&count,1)
			wg.Done()
		}()
	}
	wg.Wait()
	return atomic.LoadInt64(&count)
}
```
- テスト再実行

```console

go test ./... -race
ok  	github.com/yutachaos/go-practice/atomic/multicpu	1.526s
```
- `-race` オプションを付けて実行していますが、今回はwarningも出ずに成功しています。

### sync/atomicの問題点
- atomic.AddInt64を利用することで、atomicな計算をすることができるのですが問題点があります。

#### 忘れやすい
- 各計算を行う際に扱う値を関数にセットしないといけないので、設定漏れなどが起きやすい。

#### Int32,Int64しか対応していない。
- Int32,Int64しか対応していないので、Int32を利用した値をBoolで扱うなどのパターン、uintにしたいなどでそのまま扱いづらい。

## go.uber.org/atomic
- sync/atomicのwrapper
- atomicに扱う値を `NewXXX()` の形でstructでwrapperし、structとmethodを経由する形でAddやLoadを行う。
    - e.g https://godoc.org/go.uber.org/atomic#NewInt64 
- uber/atomicはBool、uintの値をサポートしている。
    - https://godoc.org/go.uber.org/atomic#NewBool
    - 内部的には `atomic.LoadUint32(&b.v)` をwrapperしている。
        - https://sourcegraph.com/github.com/uber-go/atomic/-/blob/atomic.go#L233  
- `go.uber.org/atomic` で書き直したversion

```golang
package multicpu_uber_atomic

import (
	"go.uber.org/atomic"
	"sync"
)

func count() int64 {

	count := atomic.NewInt64(0)
	var wg sync.WaitGroup
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			count.Add(1)
			wg.Done()
		}()
	}
	wg.Wait()
	return count.Load()
}
```

- atomicに設定した値をmethod経由でアクセスしないといけないため、設定漏れが起きづらい!

- 実行結果

```console
go test ./...
ok  	github.com/yutachaos/go-practice/atomic/multicpu_uber_atomic	0.533s
```

## おわりに
- 今回の記事で紹介した `go.uber.org/atomic` パッケージはUberのGoのguidelineで紹介されていたも
ので、他の項目もGoを書く上では役立つものがたくさんあったので、興味を持った人は是非UberのGuideも読んでみるのもおすすめです。
   - https://github.com/uber-go/guide/blob/master/style.md#use-gouberorgatomic
- 今回の記事で利用したコードは下記においてあります。
    - https://github.com/yutachaos/go-practice/tree/master/atomic

### 余談 
- 最初はGo playgroundを使って、コード実行をしようとしたら、playground上の実行環境はCPU一つで普通に成功してしまった。
    - https://play.golang.org/p/o5Y30oguQUH 

### 参考資料
- The Go Memory Model
    - https://golang.org/ref/mem
- atomicパッケージが必要な理由と使い方
    - https://qiita.com/lufia/items/35f6f1e324c09610806a
- golang の sync パッケージの使い方
    - https://mattn.kaoriya.net/software/lang/go/20140625223125.htm
- Uber go Guide use go uber atomic 
    - https://github.com/uber-go/guide/blob/master/style.md#use-gouberorgatomic