はじめに
どうも最近golangを勉強し始めたにわかgopherです。
goroutineとは本来実装が難しい並列処理がgoなら簡単に書けるよーってやつです。
並列処理自体はgo func(){...}と書くだけで実装できるのですが、それをちゃんと制御しようとするとそれなりにチャンネルとかその辺の理解が必要なので、その辺りを勉強してみました。
僕の記事より他の人の記事を参考した方がわかりやすいと思うのでこれぐらいに。。
実装
チャンネルなし
まずはgo構文だけを使って実装してみました。
package main
import (
"fmt"
"time"
)
type Task struct {
name string
processTime int
}
func (t *Task) ProcessTask(c chan string) {
fmt.Printf("Start %s\n", t.name)
for i := 0; i < t.processTime; i++ {
time.Sleep(1 * time.Millisecond)
}
fmt.Printf("End %s\n", t.name)
}
var TaskA = Task{name: "TaskA", processTime: 1000}
var TaskB = Task{name: "TaskB", processTime: 3000}
var TaskC = Task{name: "TaskC", processTime: 2000}
func Parallel() {
go TaskA.ProcessTask()
go TaskB.ProcessTask()
go TaskC.ProcessTask()
}
func main() {
Parallel()
}
で実行すると、A, B, Cの順で終わるのかなと思いきや、何も実行されないまま終わっちゃいました。。??
これはどうもmain自体もgoroutineでそれぞれのgoroutineが独立に実行され終わったかどうかは影響し合わないためのようです。
そのため別のスレッドでProcessTaskを実行してもそれが終わったかどうかに関係なくmainが終わってしまうようです。
チャンネルあり
ここで、channelというやつの登場です。これはroutineどうしで情報をやりとりするもので、ベルトコンベアのように値をひとつずつ渡していきます。
これを使うことによってその次の処理がブロックできたり、他のgoroutineと値の受け渡しができるので、並列処理を制御することができます。
送信と受信があるのですがこの辺りは以下の記事がわかりやすかったので参考にしてみるといいかもしれません。
go言語初心者が図を書きながらgoroutineやgo channelを理解する(Part1)
では、channelを実装してみます。
package main
import (
"fmt"
"time"
)
type Task struct {
name string
processTime int
}
func (t *Task) ProcessTask(c chan string) {
fmt.Printf("Start %s\n", t.name)
for i := 0; i < t.processTime; i++ {
time.Sleep(1 * time.Millisecond)
}
c <- "End " + t.name + "!\n" // 送信
}
var TaskA = Task{name: "TaskA", processTime: 1000}
var TaskB = Task{name: "TaskB", processTime: 3000}
var TaskC = Task{name: "TaskC", processTime: 2000}
func Parallel() {
c := make(chan string)
go TaskA.ProcessTask(c)
go TaskB.ProcessTask(c)
go TaskC.ProcessTask(c)
// 入る順番はgoroutineが終わった順
// channelはベルトコンベアのイメージ
m1, m2, m3 := <-c, <-c, <-c // 受信
// 全部の<-cを受け取るまで処理がブロックされてこの処理は実行されない
fmt.Println(m1, m2, m3)
}
func main() {
Parallel()
}
これを実行すると、
Start TaskC
Start TaskA
Start TaskB
End TaskA!
End TaskC!
End TaskB!
と、期待通りの出力が得られますが、"End TaskX!"は一気に表示されてしまいます。
順次表示されてほしいところですが、
m1, m2, m3 := <-c, <-c, <-c // 受信
で全ての結果を受信しているので、最後のm3を受信するまではこれ以降の処理がブロックされてしまい実行待ち状態になります。
逆にひとつずつ受信するようにすれば順次結果が出力されるようになります。
func Parallel() {
c := make(chan string)
// 以下だと順次<-cを受け取って表示される
m1 := <-c
fmt.Println(m1)
m2 := <-c
fmt.Println(m2)
m3 := <-c
fmt.Println(m3)
}
まとめ
- goroutineはchannelを使って良い感じに制御してあげないと、そもそもmainとは無関係に動くだけで何が起こっているかを知ることすらできない
- channelは良い感じに処理をブロックしたりしてくれる
全体のコードはこちら
まだまだgolangの知識が足りないのでお前ここ間違ってるぞ的なのがあればどしどしご指摘ください、、