前回から引き続いてGo言語を勉強しております南條です。
今週も業務の傍ら新しい言語を学ぶべくちょくちょくチュートリアルを進め、やっとマルチスレッドを理解し始めましたので、解説してみたいと思います。
Go言語の目玉のマルチスレッド
Go言語の特徴の一つとして、容易に軽量な並列処理を実装できるという点があります。
この記事においては、それをなるべく簡単なコードで実行し、最低限どんなコードで構成されるかを見ていきたいと思います。
Goroutine
package main
import (
"fmt"
"time"
)
func say() {
for i := 0; i < 5; i++ {
time.Sleep(1000 * time.Millisecond)
fmt.Println(i)
}
}
func main() {
go say()
fmt.Println("この文章が数字より先に表示されます")
time.Sleep(5000 * time.Millisecond)
fmt.Println("この文章は5秒後に強制的に表示されます")
}
このコードでは、16行目のsay()メソッドの呼び出しにgoというキーワードを付けることで、マルチスレッドを実現します。
つまり、メインのスレッドではsay()が終わっていなくても処理を次の行へ進めます。
なので、11行目のPrintlnが実行されて0が表示される前に、17行目の文字列が表示されます。
試しに、16行目のgoキーワードを外して実行すると、0から4までの数値が出力された後に16行目の文字列が表示されます。
16行目のPrintのコードが、15行目のsay()が完了されるまで実行されなくなるためです。
また、例えば、18行目のtime.Sleepを削除すると、go say()の内容が完了しない内にコードの終端まで処理が達してしまうため、0から4までの数字が出力されずにプログラムが終了してしまいます。
今回のコードでは、time.Sleepを用いて無理やり同期をとる形をとっていました。
同期をとるもっと確実な方法があり、それがchannelです。
Channel
package main
import (
"fmt"
"time"
)
func say(c chan bool) {
for i := 0; i < 5; i++ {
time.Sleep(1000 * time.Millisecond)
fmt.Println(i)
}
c <- true
}
func main() {
channel := make(chan bool)
go say(channel)
b := <- channel
fmt.Println(b)
}
前のコードにchannelを使った同期の機能を追加しました。
say()の引数には、c chan boolというものが追加されています。
また、channelをmain()内で作成し、say()の引数に渡しています。
これは、bool型の値を受け渡しするchannelを作成し、say()では処理の終了時にはそのchannelにbool型の値を渡すことを示しています。
また、20行目にb := <- channel
というコードが追加されています。
これは、変数bに、channelから受け取った値を代入することと、値が代入されるまで次の行に処理を進めないことを意味しています。
例えば、13行目のc <- true
を削除すると、channelに値が流れてこなくなるので、20行目から処理が進まなくなり、それを検知してエラーが発生するので、試してみましょう。
つまり、say()の処理が終わってchannelからboolの値が渡されてくるまで、20行目で処理を停止し、これによってgoroutineとの同期をとることができるのです。
まとめ
Go言語を学ぶなら並列処理のやり方は誰でも気になると思います。
実際にどんなコードを書けばそれが実行できるか、その最小単位を覚えておけば実際に利用する場面でも役立つのではないでしょうか。
また、ここで紹介したコードはGo PlaygroundにC&Cすれば実際に試せるので、コードを書き換えてみたりして挙動を確かめてみてください。