前回は、Goroutineの概要とスレッドの特性、channelについて学びました。
今回も引き続き、Goroutineについて学んでいきましょう。
前回
GoroutineとBuffered Channel
Buffred Channelで、Channelに保持しておく値の数を制限できます。
1 2 3 4 5 6 7 8 9 10 11 12 |
package main import "fmt" func main() { // 2つまでOK ch := make(chan int, 2) ch <- 100 fmt.Println(len(ch)) ch <- 200 fmt.Println(len(ch)) } |
上記のサンプルでは、バッファの数を「2」と指定し、「ch <- num」にてchannelに値を格納しています。
プログラムを実行してみましょう。
1 2 3 |
$ go run main.go 1 2 |
次に、channelに対して、3つ目の値を格納してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package main import "fmt" func main() { // スレッドが2つまでOK ch := make(chan int, 2) ch <- 100 fmt.Println(len(ch)) ch <- 200 fmt.Println(len(ch)) ch <- 300 // 追加 fmt.Println(len(ch)) } |
プログラムを実行するとdeadlockが発生します。
1 2 3 4 5 6 7 8 9 |
$ go run main.go 1 2 fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan send]: main.main() /Users/hoge/go/src/github.com/hoge/main.go:12 +0x16d exit status 2 |
この問題を回避するには、3つ目のデータを格納する前に、バッファからデータを取り出す必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package main import "fmt" func main() { // スレッドが2つまでOK ch := make(chan int, 2) ch <- 100 fmt.Println(len(ch)) ch <- 200 fmt.Println(len(ch)) get := <-ch fmt.Printf("get %d\n", get) fmt.Println(len(ch)) ch <- 300 fmt.Println(len(ch)) } |
1 2 3 4 5 6 |
$ go run main.go 1 2 get 100 1 2 |

要するに、指定したバッファ数をオーバーしなければOKです。
Channelとrangeメソッド
rangeメソッドを使うと繰り返し処理が容易に実装できます。
Channelもループ処理を行うことが可能ですが、少し注意する点があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package main import "fmt" func main() { // スレッドが2つまでOK ch := make(chan int, 2) ch <- 100 ch <- 200 for c := range ch { fmt.Println(c) } } |
上記のプログラムを実行するとどうなるでしょうか?
答えは、deadlockを引き起こします。
1 2 3 4 5 6 7 8 9 |
$ go run main.go 100 200 fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.main() /Users/hoge/go/src/github.com/hoge/main.go:11 +0xfa exit status 2 |
Buffered Channelとして指定したバッファ数は「2」ですが、rangeでループを回した時に3つめのデータの取得を行おうとして、deadlockが発生するようです。
この場合は、rangeでループを回す前にchannelを閉じる必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package main import "fmt" func main() { // スレッドが2つまでOK ch := make(chan int, 2) ch <- 100 ch <- 200 // channekを閉じる close(ch) for c := range ch { fmt.Println(c) } } |
プログラムを実行してみましょう。
1 2 3 |
$ go run main.go 100 200 |
closeメソッドを使うとchannelを閉じることができるので、rangeにて不必要なchannelの読み込みを行わなくなります。
このcloseメソッドは、関数を跨っても有効です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package main import "fmt" func pro1(datas []int, c chan int) { sum := 0 for _, data := range datas { sum += data c <- sum } close(c) } func main() { datas := []int{1, 2, 3, 4, 5} ch := make(chan int, len(datas)) go pro1(datas, ch) for c := range ch { fmt.Println(c) } } |
1 2 3 4 5 6 |
$ go run main.go 1 3 6 10 15 |
GoroutineとProducer/Consumerパターン
Producer/Consumerパターンを学びましょう。
mainスレッドからProducer,Consumerのスレッドを複数立ち上げ、Producerの中で実行した結果をConsumerを経由して取り出します。
サンプルを以下に記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
package main import ( "fmt" "sync" ) func producer(ch chan int, i int) { ch <- i * 2 } func consumer(ch chan int, wg *sync.WaitGroup) { for i := range ch { fmt.Println("process", i*1000) wg.Done() } } func main() { var wg sync.WaitGroup ch := make(chan int) // Producer - プロセスを10個立ち上げる for i := 0; i < 10; i++ { wg.Add(1) go producer(ch, i) } // Consumer - Producerで処理した結果を取り出す go consumer(ch, &wg) wg.Wait() // consumer自体がgoroutineなのでrangeでchannelから値を取り出そうとして待機している // 処理を終了するために、mainスレッドにcloseを実装する必要がある close(ch) } |
実行します。
1 2 3 4 5 6 7 8 9 10 11 |
$ go run main.go process 0 process 4000 process 2000 process 12000 process 6000 process 10000 process 16000 process 14000 process 8000 process 18000 |
尚、consumerは、以下のようにも実装できます。
1 2 3 4 5 6 7 8 |
func consumer(ch chan int, wg *sync.WaitGroup) { for i := range ch { func() { defer wg.Done() fmt.Println("process", i*1000) }() } } |
このようにすると予期せぬエラーで処理が終了しても、defer wg.Doneでgoroutineの完了を知らせることが可能です。
次回
次回も引き続き、goroutineを学びましょう。
最近のコメント