goroutine は非同期に実行される処理、channel はその groutine と通信する為の仕組みと考えると分かりやすいです。
package main
import (
"fmt"
"time"
)
func main() {
task := make(chan string)
taskquit := make(chan bool)
workerquit := make(chan bool)
go func() {
loop:
for {
select {
case <-taskquit:
workerquit <- true
break loop
case job := <-task:
fmt.Println(job)
}
}
}()
go func() {
for n := 0; n < 3; n++ {
task <- fmt.Sprintf("お仕事%03d", n+1)
time.Sleep(1 * time.Second)
}
taskquit <- true
}()
<-workerquit
}
この様に goroutine との間でキューの役割を果たします。channel はバッファを持っておりある程度の量ならばブロックする事無く goroutine と通信出来ます(バッファサイズはmakeで変更可能)。この goroutine と channel をどの様に使うかですが、まず非同期を意識せずに主たる目的を持って処理を書きます。
- ある処理から「お仕事」が3回投げかけられる
- ある処理では「お仕事」の依頼を受けると逐次処理する
しかし groutine と channel を使う事で
- お仕事の依頼を投げる側
- お仕事の依頼を受ける側
task <- fmt.Sprintf("お仕事%03d", n+1)
そしてお仕事を受ける側はその channel を指定して「お仕事」を受け取ります。しかしお仕事を受ける側は、永遠にループする訳にはいけません。終了指示があれば中途半端にお仕事を処理せずに、グレースフルに終了したい物です。そこで終了用の channel を用意して、それを両方 select します。
例ではタスク登録 goroutine 終了用とワーカー終了用を用意しました。
select {
case <-taskquit:
workerquit <- true
break loop
case job := <-task:
fmt.Println(job)
}
元々、別の関数で実装されていた処理というのは並行でない事はもちろんですが、多くのメモリを必要とします。さらにそれをコールバックベースの処理に作り替えると、大きくコードを壊さなければなりません。また純粋に並行ではありません。golang の goroutine と channel を使えば、既存の処理を簡単に並行化する事が出来るのです。
今回の例では両方共に goroutine にしましたが、この目的で言えばお仕事を投げる側は goroutine でなくても構いません。
例えば Web でデータを登録し、それをバックグランドジョブとして後処理したいといった場合に、この channel の威力が発揮されるでしょう。
追記
先にワーカーが終了しないケースがあったのでコードを修正。