はじめに
「Go言語による並行処理」を読んでいます。
しかし、なかなか難しいので自分用のメモとしてアウトプットしていきます。
説明形式で記事を書きますので、これからGo言語の並行処理をやる人は参考になるかもしれません。
並行処理とは?
Go言語の並行処理プログラミング
並行処理プログラミングには、一般的に大きく二つの方法があります。
・Shared-memory communication
メモリを共有する事で通信をやりとりする事
・Message-passing communication
通信によって(仲介業者を通して)メモリをやりとりする事
Do not communicate by sharing memory; instead, share memory by communicating.
Go言語ではMessage-passing communicationを推奨しているそうです。
並列処理と並行処理の違いとは?
・Concurrent(並行)
複数の動作が、論理的に、順不同もしくは同時に起こりうること
・Parallel(並列)
複数の動作が、物理的に、同時に起こること
工場で10人メンバーが仕事をしているとしたら。。。
10人が同時に同じ木を切っている状態。これは並列処理です。
1人は木を切って、もう一人は金槌を叩いている、そしてもう一人は・・・。これは並行処理です。
そしてGo言語ではConcurrent(並行)を採用しているそうです。
※詳細なイメージは以下のサイトが役に立ちました。
parallel と concurrent、並列と並行の違い
「なんとなく」で終わらせてませんか?"いらすと"で覚える並列と並行の違い
Goroutine詳細
Goroutineとは?
Goroutineとは、一言で言うと「並行に動作している関数のこと」。
一番最初に呼び出されるfunc main(){}
はmain Goroutineと呼ばれるものです。
func main() {
go hoge()
go fuga()
go piyo()
}
こちらのコードを説明します。
まずはメインGoroutineが呼ばれます。
そしてhoge,fuga,piyoのGoroutineが並行で呼ばれています。
このコードからわかるようにmain Goroutine以外のGoroutineの呼び出し方は関数にgo
を付けるだけです。
Goroutineの挙動
func main() {
fmt.Println("main start")
go Greet(1)
go Greet(2)
go Greet(3)
// time.Sleep(time.Second) ←追加したらちゃんと動く
fmt.Println("main end")
}
func Greet(i int) {
fmt.Println("hello",i)
}
// main start
// hello 3
// hello 2
// hello 1
// main end
time.Sleepが存在しない時は、main start と main endのみが表示されます。
各々のGoroutineはプロセスが終了するまで存在し続けます。
そしてmain Goroutineが終了した時に全体のプロセスが終了します。
つまり、main start と main endしか表示されなかった理由は各Goroutineより先にmain Goroutineが終了したことがわかります。
なのでtime.Sleepを使いmain Goroutineの終了を待つことによって各Goroutineの処理が先に行われて正しく動きました。
※Goroutineは順不同なので(並行だから)こういった1秒止めるなどはNGです。
GoroutineとShared Memory
func main() {
fmt.Println("main start")
for i := 0; i < 5; i++ {
go func() {
// iを参照し続ける
Greet(i)
}()
}
time.Sleep(time.Second)
fmt.Println("main end")
}
func Greet(i int) {
fmt.Println("hello",i)
}
// main start
// hello 5
// hello 5
// hello 5
// hello 5
// hello 5
// main end
・Shared-memory・・・メモリを共有して通信をやりとりする事
・Message-passing・・通信によって(第三者を通して)メモリをやりとりする事
と上記で学びました。
各GoroutineがShared-memoryを使用すると、このような動作になります。
これはゴルーチンがスケジュールされた時のi値を参照見るので最後に代入された5をみています。
参照ではなくコピーを渡すことで回避できます。
go func(i int) {
// iを参照し続ける
Greet(i)
}(i)
こういったメモリの事を考えながら実装するのはとても難しいです。
なのでShared-memoryではなくMessage-passing(仲介業者を通して)の仕組みで実装していく必要があります。
ここで言う第三者とはchannelの事です。
最後に
goroutineについてすごく基礎的なところをまとめてみました。
次回はチャネルについてまとめていきます。