Goルーチンで並行化する方法: 6秒かかる処理を3秒にしよう


# この記事の目的

6秒かかる直列処理を並行処理に改修し、3秒で終わるようにする

# 準備

まずは、適当にフォルダを作る

```
mkdir ~/Desktop/goroutine
cd ~/Desktop/goroutine/
```

つぎに、main.goファイルを作る

```
touch main.go
```

# まず直列実行するプログラムを書く

* 1秒待つコマンド
* 2秒待つコマンド
* 3秒待つコマンド

上記を順番に実行していくプログラムを `main.go` の中身を書く。
つまり、合計で6秒かかってしまう。

```go:main.go
package main

import (
	"log"
	"time"
)

func main() {
	log.Print("started.")

	// 1秒かかるコマンド
	log.Print("sleep1 started.")
	time.Sleep(1 * time.Second)
	log.Print("sleep1 finished.")

	// 2秒かかるコマンド
	log.Print("sleep2 started.")
	time.Sleep(2 * time.Second)
	log.Print("sleep2 finished.")

	// 3秒かかるコマンド
	log.Print("sleep3 started.")
	time.Sleep(3 * time.Second)
	log.Print("sleep3 finished.")

	log.Print("all finished.")
}


```

ともあれ、実行してみる

```
% go run main.go
2013/12/05 15:02:09 started.
2013/12/05 15:02:09 sleep1 started.
2013/12/05 15:02:10 sleep1 finished.
2013/12/05 15:02:10 sleep2 started.
2013/12/05 15:02:12 sleep2 finished.
2013/12/05 15:02:12 sleep3 started.
2013/12/05 15:02:15 sleep3 finished.
2013/12/05 15:02:15 all finished.
```

やはり、6秒かかった。

これを並行化したい。

# Goルーチンを使って並行化し、3秒で終わるようにする

Go言語にはGoルーチンという処理の並行化を簡単にできる仕組みがある。クロージャーや関数に `go` をつけるだけなのでシンプル。

ただし、Goルーチンだけでは、並行化した処理が終わる前に、メインの処理が終わってしまう。つまり、待ってくれない。Goルーチンが終わるまで待つには、「チャネル」という仕組みを使う。

なお、各ルーチンの実行結果を、呼び出し元に戻すときにもチャネルを使う。今回は、特に実行結果は必要ないので、チャネルに適当な値を入れることにする。ここでは、とりあえずbool値にする。

以上を踏まえて、さきほどの `main.go` を改修する:

```go:main.go
package main

import (
	"log"
	"time"
)

func main() {
	log.Print("started.")

	// チャネル
	sleep1_finished := make(chan bool)
	sleep2_finished := make(chan bool)
	sleep3_finished := make(chan bool)

	go func() {
		// 1秒かかるコマンド
		log.Print("sleep1 started.")
		time.Sleep(1 * time.Second)
		log.Print("sleep1 finished.")
		sleep1_finished <- true
	}()

	go func() {
		// 2秒かかるコマンド
		log.Print("sleep2 started.")
		time.Sleep(2 * time.Second)
		log.Print("sleep2 finished.")
		sleep2_finished <- true
	}()

	go func() {
		// 3秒かかるコマンド
		log.Print("sleep3 started.")
		time.Sleep(3 * time.Second)
		log.Print("sleep3 finished.")
		sleep3_finished <- true
	}()

	// 終わるまで待つ
	<- sleep1_finished
	<- sleep2_finished
	<- sleep3_finished

	log.Print("all finished.")
}
```

実行してみよう

```
% go run main.go
2013/12/05 15:14:58 started.
2013/12/05 15:14:58 sleep1 started.
2013/12/05 15:14:58 sleep2 started.
2013/12/05 15:14:58 sleep3 started.
2013/12/05 15:14:59 sleep1 finished.
2013/12/05 15:15:00 sleep2 finished.
2013/12/05 15:15:01 sleep3 finished.
2013/12/05 15:15:01 all finished.
```

並行化ができて、3秒で終わるようになった＼(^o^)／



# チャネルが冗長なので1つにしたい…

`sleep1_finished`、`sleep2_finished`、`sleep3_finished`の3つのチャネルを作ったが、もっとエレガントにチャネルしたい。


```go:main.go
package main

import (
	"log"
	"time"
)

func main() {
	log.Print("started.")

	// チャネル
	finished := make(chan bool)

	go func() {
		// 1秒かかるコマンド
		log.Print("sleep1 started.")
		time.Sleep(1 * time.Second)
		log.Print("sleep1 finished.")
		finished <- true
	}()

	go func() {
		// 2秒かかるコマンド
		log.Print("sleep2 started.")
		time.Sleep(2 * time.Second)
		log.Print("sleep2 finished.")
		finished <- true
	}()

	go func() {
		// 3秒かかるコマンド
		log.Print("sleep3 started.")
		time.Sleep(3 * time.Second)
		log.Print("sleep3 finished.")
		finished <- true
	}()

	// 終わるまで待つ
	<-finished
	<-finished
	<-finished

	log.Print("all finished.")
}
```

チャネルはひとつにできたが、待つところで3回待つ必要がある…。
10並行したら10回 `<-finished` を書かないといけないのは面倒だし、エンバグしそうなので、回数を指定したい。

# 「終わるまで待つ」ところを回数指定にする

とりあえず無骨に `for` で3回 `<-finished` を実行すれば良いようだ

``` go:main.go
package main

import (
	"log"
	"time"
)

func main() {
	log.Print("started.")

	// チャネル
	finished := make(chan bool)

	go func() {
		// 1秒かかるコマンド
		log.Print("sleep1 started.")
		time.Sleep(1 * time.Second)
		log.Print("sleep1 finished.")
		finished <- true
	}()

	go func() {
		// 2秒かかるコマンド
		log.Print("sleep2 started.")
		time.Sleep(2 * time.Second)
		log.Print("sleep2 finished.")
		finished <- true
	}()

	go func() {
		// 3秒かかるコマンド
		log.Print("sleep3 started.")
		time.Sleep(3 * time.Second)
		log.Print("sleep3 finished.")
		finished <- true
	}()

	// 終わるまで待つ
	for i := 1; i <= 3; i++ {
		<-finished
	}

	log.Print("all finished.")
}

```

実行してみる:

```
% go run main.go
2013/12/05 15:32:40 started.
2013/12/05 15:32:40 sleep2 started.
2013/12/05 15:32:40 sleep1 started.
2013/12/05 15:32:40 sleep3 started.
2013/12/05 15:32:41 sleep1 finished.
2013/12/05 15:32:42 sleep2 finished.
2013/12/05 15:32:43 sleep3 finished.
2013/12/05 15:32:43 all finished.
```

# 回数指定じゃなくて、ルーチンの数だけ待ちたい

`<-finsihed` を列挙するよりも、回数指定のほうが並行数の増減に対応しやすいが、ルーチンの数に応じて待つようにしたい。

どうやってやるかだが、クロージャーを配列にして、要素の数だけGoルーチンを開始し、要素の数だけ `<-finsihed` を実行するようにする。この変更を加えたコードが下記になる。

```go:main.go
package main

import (
	"log"
	"time"
)

func main() {
	log.Print("started.")

	finished := make(chan bool)

	// 配列
	funcs := []func(){
		func() {
			// 1秒かかるコマンド
			log.Print("sleep1 started.")
			time.Sleep(1 * time.Second)
			log.Print("sleep1 finished.")
			finished <- true
		},
		func() {
			// 2秒かかるコマンド
			log.Print("sleep2 started.")
			time.Sleep(2 * time.Second)
			log.Print("sleep2 finished.")
			finished <- true
		},
		func() {
			// 3秒かかるコマンド
			log.Print("sleep3 started.")
			time.Sleep(3 * time.Second)
			log.Print("sleep3 finished.")
			finished <- true
		},
	}

	// 並行化する
	for _, sleep := range funcs {
		go sleep()
	}

	// 終わるまで待つ
	for i := 0; i < len(funcs); i++ {
		<-finished
	}

	log.Print("all finished.")
}
```

実行してみる:

```
% go run main.go
2013/12/05 16:30:18 started.
2013/12/05 16:30:18 sleep1 started.
2013/12/05 16:30:18 sleep2 started.
2013/12/05 16:30:18 sleep3 started.
2013/12/05 16:30:19 sleep1 finished.
2013/12/05 16:30:20 sleep2 finished.
2013/12/05 16:30:21 sleep3 finished.
2013/12/05 16:30:21 all finished.
```

# まとめ

6秒かかる処理を、Goルーチンとチャネルを組み合わせて並行化し、3秒で終わるようになった。

# 課題

最後の `for` あたりをもっとシンプルにする方法はないものか?

# UPDATE 2013/12/06 チャネルを使わずに待つ方法

調べてみたら [sync.WaitGroup] というモジュールがあることがわかった。これを使うとチャネルを宣言しなくても、処理を待つことができる。

```go:main.go
package main

import (
	"log"
	"sync"
	"time"
)

func main() {
	log.Print("started.")

	// 配列
	funcs := []func(){
		func() {
			// 1秒かかるコマンド
			log.Print("sleep1 started.")
			time.Sleep(1 * time.Second)
			log.Print("sleep1 finished.")
		},
		func() {
			// 2秒かかるコマンド
			log.Print("sleep2 started.")
			time.Sleep(2 * time.Second)
			log.Print("sleep2 finished.")
		},
		func() {
			// 3秒かかるコマンド
			log.Print("sleep3 started.")
			time.Sleep(3 * time.Second)
			log.Print("sleep3 finished.")
		},
	}

	var waitGroup sync.WaitGroup

	// 関数の数だけ並行化する
	for _, sleep := range funcs {
		waitGroup.Add(1) // 待つ数をインクリメント

		// Goルーチンに入る
		go func(function func()) {
			defer waitGroup.Done() // 待つ数をデクリメント
			function()
		}(sleep)

	}

	waitGroup.Wait() // 待つ数がゼロになるまで処理をブロックする

	log.Print("all finished.")
}

```

実行結果:

```
% go fmt main.go && go run main.go
2013/12/06 16:14:40 started.
2013/12/06 16:14:40 sleep1 started.
2013/12/06 16:14:40 sleep2 started.
2013/12/06 16:14:40 sleep3 started.
2013/12/06 16:14:41 sleep1 finished.
2013/12/06 16:14:42 sleep2 finished.
2013/12/06 16:14:43 sleep3 finished.
2013/12/06 16:14:43 all finished.
```

[sync.WaitGroup]: http://golang.org/pkg/sync/#WaitGroup