並行処理(concurrent computing)は、複数の処理を同時に行うことです。並行処理と間違いやすいものに並列処理(parallel computing)があります。並列処理は一つの処理を複数の CPU で行うことです。また、コンピュータが行う処理の最小単位のことをタスク(task)と言います。
Go で並行処理を行うには、ゴールーチン(goroutine)とチャネル(channel)という方法を使います。まず、この章では、goroutine について説明します。
goroutine の説明の前に、普通の逐次処理についてみていきます。逐次(ちくじ)処理とは、コードの書かれた順番に一つ一つ処理が行われていくことです。順次処理、シリアル処理とも呼ばれます。特に指示をしなければ、プログラムは逐次処理で実行されます。
package main
import (
"fmt"
"time" // 時間を経過させるために time パッケージが必要です
)
func main() {
fmt.Println("started")
longSleep ()
shortSleep()
time.Sleep(time.Second * 6) // 時間を 6 秒経過させています
fmt.Println("main sleep finished")
}
func longSleep() {
time.Sleep(time.Second * 4) // 時間を 4 秒経過させています
fmt.Println("long sleep finished")
}
func shortSleep() {
time.Sleep(time.Second * 2) // 時間を 2 秒経過させています
fmt.Println("short sleep finished")
}
started
long sleep finished // 4 秒経過してから表示されます
short sleep finished // さらに 2 秒経過してから表示されます
main sleep finished // そしてさらに 6 秒経過してから表示されます
main 関数が始まって、最初に longSleep 関数が呼ばれます。longSleep は 4 秒経過してから文字列を表示して処理を終えます。
次に、shortSleep 関数が呼ばれます。shortSleep は 2 秒経過してから文字列を表示して処理を終えます。
最後に main 関数でも 6 秒経過してから文字列を表示してプログラムを終了しています。
前述の、serial.go を、goroutine を使った並行処理に変更するのは簡単です。longSleep 関数と shortSleep 関数の前に go と記述するだけです。
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("started")
go longSleep () // longSleep () の前に go と記述しています
go shortSleep() // shortSleep() の前に go と記述しています
time.Sleep(time.Second * 6)
fmt.Println("main sleep finished")
}
func longSleep() {
time.Sleep(time.Second * 4)
fmt.Println("long sleep finished")
}
func shortSleep() {
time.Sleep(time.Second * 2)
fmt.Println("short sleep finished")
}
started
short sleep finished // プログラム開始後 2 秒経過してから表示されます
long sleep finished // プログラム開始後 4 秒経過してから表示されます
main sleep finished // プログラム開始後 6 秒経過してから表示されます
go を付けた関数は同時に処理されます。ここでは、処理時間の短い shortSleep のほうが先に処理を終えています。
main 関数には暗黙的に最初から go が記述された状態になっています。
main 関数のスリープ時間をなくしてみたらどうなるでしょうか。
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("started")
go longSleep () // computing1() の前に go と記述しています
go shortSleep() // computing2() の前に go と記述しています
//time.Sleep(time.Second * 6) コメントアウトで無効化しています
fmt.Println("main sleep finished")
}
func longSleep() {
time.Sleep(time.Second * 4)
fmt.Println("long sleep finished")
}
func shortSleep() {
time.Sleep(time.Second * 2)
fmt.Println("short sleep finished")
}
started
main sleep finished
main 関数は最初から goroutine になっていますので、main 関数、longSleep 関数、shortSleep 関数が同時に処理されることになります。
main のスリープを外したために、main 関数が最初に終了します。
main 関数の終了はプログラムの終了なので、longSleep と shortSleep は処理を終えないままプログラムは終了します。
実行結果がすっきりするようにしてみました。
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("started")
go longSleep ()
go shortSleep()
time.Sleep(time.Second * 6)
fmt.Println("main sleep finished")
time.Sleep(time.Second * 2)
fmt.Println("finished")
}
func longSleep() {
time.Sleep(time.Second * 4)
fmt.Println("long sleep finished")
}
func shortSleep() {
time.Sleep(time.Second * 2)
fmt.Println("short sleep finished")
}
started
short sleep finished
long sleep finished
main sleep finished
finished