Goがなぜ並行処理に長けているのか、Gorutineのどのような部分が良いのかについて解説をしていきます。
スタック、ヒープ
Goroutineの良さを知るためにはまずスタック、ヒープを知っておく必要があります。
スタックは引数や変数、返り値、静的な構造体など静的なデータが格納されていて、OSによって管理がされている。
ヒープは関数スコープにとらわれず、オブジェクト持ち続けていて、ガベージコレクションなどソフトウェア側で管理されている。
データのアクセスはスタックが高速になる。
Goroutineについて
Goroutineは主に2つの特徴があります。
- メモリ効率が良い
- スイッチングコストが低い
それぞれについて解説していきます。
メモリ効率が良い
スレッドの場合は、スレッド間でメモリが干渉し合わないようにスタックガードページと呼ばれる1MBの領域を確保している。Goroutineはコンパイラ側で必要に応じてヒープ領域を割り当てるようにしているため、スタック領域2KBしか消費しないで済み、メモリ効率が良い。
スイッチングコストが低い
OSスレッドはコンテキストスイッチによるオーバーヘッドが大きい。
それに比べて、GoランタイムはM個のGoroutineをN個のOSスレッドで実行するM:Nモデルを採用している。Goのランタイムによって多くのGoroutineがOSの1スレッド上で多重化されている。これによりGoroutineは生成するのも切り替えるのも安価になる。GoのプロセスごとのOSのスレッドは比較的数が少なくなっていて、Goのランタイムは使われてないOSスレッドに実行可能なGorutineを割り当てる。
問題としてスケジューリングをするのが複雑になるが、Goの場合はGoランタイムスケジューラーがこの問題を解決してくれている。
Goランタイムスケジューラー
Goランタイムスケジューラーは、Gorutineの数がOSスレッド数を上回ってしまった場合にリソースうまく振り分ける役目をする。
Goランタイムのコンポーネントとして、以下がある。
- G(Gorutine)
- M(Machine):OSスレッド
- P(Processor):Goroutineを実行するのに必要なPCリソース。OSスケジューラにとってCPUのようなもの
Goランタイムスケジューラのイメージ図
Gの実行には3パターンある
- グローバルキューに実行可能なGがないかを確認、あればそのGを実行
- 1でGがない場合、動いているPのローカルキューに実行可能なGがあるかを確認、あればそのGを実行
- キューにGがなかった場合は、他を見つけに行き、忙しいPからGを横取りする
Goroutineを使う上での注意点
- WaitGroup、channelなどを使用して、終了をブロックをする必要がある。
- 配列やマップなどはアトミックな操作ができないため、使用する時にはスレッドセーフにする。RWMutexを使用する。
- 順番通りに実行されないので、順番を気にする場合はGoroutineは使用しない。
- リソースを無限に使われないために、必要あればGoroutineの同時実行数を制限する必要がある。
理解度チェック
今回の内容がわかっているかの問題を作成しています。
問題をクリックすると、答えが表示されますので、今回の内容を理解できたかチェックをしてみてください。
まとめ
今回はGoroutineがなぜ良いのか、また使用するときの注意点について紹介をしてきました。
注意点で、Goroutineの同時実行数の制限をかける必要があると書きましたが、その対応について記事を書いていたり、他にも低レイヤーな内容を書いている記事もありますので、興味があればそちらも読んでみてください。
【おすすめ記事のリンク】
コメント