Go言語による並行処理の理解を深めるために、ライブラリcheggaaa/pbを読んでみた

昨日は、 @srttk さんの「net/http でサーバーを立て、いくつかのパターンをパースしてみる」でした。
標準パッケージで、ライブラリが実装していること（URLに含まれるパラメータのパース）の実現のための方法が分かりました！「標準パッケージでどうやるの」と考え模索することでGo言語におけるノウハウが得られますよね！自分も定期的に、そのような時間を作りたいとおもいました。

## これは何？

[cheggaaa/pb](https://github.com/cheggaaa/pb)(progress barのライブラリ)のコードを読んでみたメモです。
並行処理についてのノウハウをまとめました。

## モチベーション

[Go言語による並行処理](https://www.oreilly.co.jp/books/9784873118468/)を読んで、並行処理についての理解を深めたかったからです。
なにかサクッと作りたいな〜と思い、ヌルヌル動くプログレスバーを作ろうと思いつくも、すでに良いライブラリ（cheggaaa/pb）があることを知り、これを読んで理解を深めたいと思いました。

## pbのコードを読んでみた

### 使い方（READMEより）

下記のとおり、`bar := pb.StartNew(`するとプログレスバーの表示が始まり、処理が進むごとに`bar.Increment()`するだけ！cliツールを作成する際に、かなり簡単に利用できそうです。
([公式README](https://github.com/cheggaaa/pb)より)

```golang
package main

import (
	"gopkg.in/cheggaaa/pb.v1"
	"time"
)

func main() {
	count := 100000
	bar := pb.StartNew(count)
	for i := 0; i < count; i++ {
		bar.Increment()
		time.Sleep(time.Millisecond)
	}
	bar.FinishPrint("The End!")
}
```

上記のサンプルは、下記のように動きます。

![pb_world.gif](https://qiita-image-store.s3.amazonaws.com/0/120047/20eb74ba-6c21-f15f-45df-5c48eaf11743.gif)


このライブラリは、主に下記の実装がされているようでした。

* プログレスバー初期化
* プログレスバー表示開始
* 処理の進捗をプログレスバーに反映
* プログレスバーの再描画

### プログレスバー初期化

このライブラリの主な情報は、構造体`ProgressBar`に保持されます。
たとえば、進捗度合いを管理するための情報として、処理全体の値`Total　`と、現時点の進捗値`current` をもっています。


```golang
// at https://github.com/cheggaaa/pb/blob/751f9183c336d1bb8ef77beb95243f367d1d90e1/pb.go#L68

type ProgressBar struct {
current  int64 // current must be first member of struct (https://code.google.com/p/go/issues/detail?id=5278)
//〜省略〜
Total    int64
//〜省略〜
}
```

この構造体の初期化処理で、Totalを設定できるようになっています。

```golang
// at https://github.com/cheggaaa/pb/blob/751f9183c336d1bb8ef77beb95243f367d1d90e1/pb.go#L38

// Create new progress bar object using int64 as total
func New64(total int64) *ProgressBar {
	pb := &ProgressBar{
		Total:           total,
//〜省略〜
	}
	return pb.Format(FORMAT)
}
```

### プログレスバー表示開始

`bar := pb.StartNew(`では、Newしたあと、`Start()`を実行するようです

```golang
// at https://github.com/cheggaaa/pb/blob/751f9183c336d1bb8ef77beb95243f367d1d90e1/pb.go#L56

// Create new object and start
func StartNew(total int) *ProgressBar {
	return New(total).Start()
}
```


`Start()`はざっくり、
1.`pb.Update()`で初回のプログレスバーを描画して、
2.`go pb.refresher()`という再描画タスクを実行する処理のようです。

```golang
// at https://github.com/cheggaaa/pb/blob/751f9183c336d1bb8ef77beb95243f367d1d90e1/pb.go#L114

// Start print
func (pb *ProgressBar) Start() *ProgressBar {
	pb.startTime = time.Now()
	pb.startValue = atomic.LoadInt64(&pb.current)
	if atomic.LoadInt64(&pb.Total) == 0 {
		pb.ShowTimeLeft = false
		pb.ShowPercent = false
		pb.AutoStat = false
	}
	if !pb.ManualUpdate {
		pb.Update() // Initial printing of the bar before running the bar refresher.
		go pb.refresher()
	}
	return pb
}

```


### 処理の進捗をプログレスバーに反映

このライブラリ利用者は、`bar.Increment()`とすれば、プログレスバーに進捗状況を反映できました。これは下記のように、`&pb.current`のフィールドに1を加算している処理のようです。

```golang
// at https://github.com/cheggaaa/pb/blob/751f9183c336d1bb8ef77beb95243f367d1d90e1/pb.go#L136
// Increment current value
func (pb *ProgressBar) Increment() int {
	return pb.Add(1)
}

// at https://github.com/cheggaaa/pb/blob/751f9183c336d1bb8ef77beb95243f367d1d90e1/pb.go#L151
// Add to current value
func (pb *ProgressBar) Add(add int) int {
	return int(pb.Add64(int64(add)))
}

func (pb *ProgressBar) Add64(add int64) int64 {
	return atomic.AddInt64(&pb.current, add)
}
```


### プログレスバーの再描画

`Start()`にて、`go pb.refresher()`が実行されていました。ここでプログレスバーが再描画されるようです。
`pb.refresher()`は、`pb.RefreshRate`ごとに、`Update()`を実行する関数のようです。

```golang
// at https://github.com/cheggaaa/pb/blob/751f9183c336d1bb8ef77beb95243f367d1d90e1/pb.go#L494

// Internal loop for refreshing the progressbar
func (pb *ProgressBar) refresher() {
	for {
		select {
		case <-pb.finish:
			return
		case <-time.After(pb.RefreshRate):
			pb.Update()
		}
	}
}
```

`Update()`は、`Write the current state of the progressbar`のコメントどおり、プログレスバーを描画する関数のようです。実際に描画する処理は、`pb.write(`に集約されていそうです。

```golang
// at https://github.com/cheggaaa/pb/blob/751f9183c336d1bb8ef77beb95243f367d1d90e1/pb.go#L442:24

// Write the current state of the progressbar
func (pb *ProgressBar) Update() {
	c := atomic.LoadInt64(&pb.current)
	p := atomic.LoadInt64(&pb.previous)
	t := atomic.LoadInt64(&pb.Total)
	if p != c {
		pb.mu.Lock()
		pb.changeTime = time.Now()
		pb.mu.Unlock()
		atomic.StoreInt64(&pb.previous, c)
	}
	pb.write(t, c)
	if pb.AutoStat {
		if c == 0 {
			pb.startTime = time.Now()
			pb.startValue = 0
		} else if c >= t && pb.isFinish != true {
			pb.Finish()
		}
	}
}

```

`pb.write()`は長いですが、
* 1.変数`out`にプログレスバーを設定し、
* 2.outに設定された文字列を出力　しているようです

```golang
// at https://github.com/cheggaaa/pb/blob/751f9183c336d1bb8ef77beb95243f367d1d90e1/pb.go#L279
func (pb *ProgressBar) write(total, current int64) {
//〜省略〜
			totalS := Format(total).To(pb.Units).Width(pb.UnitsWidth)
			countersBox = fmt.Sprintf(" %s / %s ", current, totalS)
// 1.1変数`out`にプログレスバーを設定
// at https://github.com/cheggaaa/pb/blob/751f9183c336d1bb8ef77beb95243f367d1d90e1/pb.go#L400
	out = pb.prefix + timeSpentBox + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix

// 2.outに設定された文字列を出力
// at https://github.com/cheggaaa/pb/blob/751f9183c336d1bb8ef77beb95243f367d1d90e1/pb.go#L418
	switch {
//〜省略〜
	case pb.Callback != nil:
		pb.Callback(out + end)
	case !pb.NotPrint:
		fmt.Print("\r" + out + end)
	}
```

## 並行処理に関するノウハウ

上記のように、このライブラリの基本動作が分かったので、並行処理に関する学びをまとめます。

### 基本コンセプト

再描画のためのgoroutineを走らせ、Durationごとに再描画させている」という方針だということがわかりました。
ツールを作る際に、この辺の設計コンセプトをつくるのに経験が必要だと思いました。


### sync.Mutex

[sync.Mutex](https://godoc.org/github.com/aykevl/tinygo/src/sync#Mutex)
`mu.Lock()`できるのは、１箇所のみ。他の箇所でLockされているときにLockがcallされたら、ほか箇所のLockが`mu.Unlock()`されるまでブロックされる。

### sync/atomic

[sync/atomic](https://golang.org/pkg/sync/atomic/)

```
Package atomic provides low-level atomic memory primitives useful for implementing synchronization algorithms.
```

これは、goroutine内部における、主に数値系の競合を避けるためのパッケージのようです。
sync/atomicについて、golang.tokyo#14で発表されていた morikuni さんの資料[ホリネズミでもわかるGoroutine入門 ](https://speakerdeck.com/morikuni/golang-dot-tokyo-number-14?slide=47)でも言及されているようですので、こちらを読むと理解が深まりました。

## まとめ

* コードを書く時間も学習のためには必要だけど、良いコードを読む時間も大切！
* morikuni さんの資料[ホリネズミでもわかるGoroutine入門 ](https://speakerdeck.com/morikuni/golang-dot-tokyo-number-14)がめっちゃわかりやすかった
* 他に並行処理に関して良い学習方法があれば、教えてください！

明日は、@Baki33 さんです。ぜひお楽しみに！