ただし、スレッド プールは、ロジック作成者にスレッド割り当ての抽象化メカニズムを提供します。ただし、いつでもどこでも発生する可能性のある同時実行性とスレッド処理の要件に直面した場合、スレッド プールはあまり直感的で便利ではありません。メカニズムはありますか。ユーザーが十分なタスクを割り当てると、システムはユーザーがタスクを CPU に割り当てるのを自動的に支援し、これらのタスクが可能な限り同時に実行できるようにします。この仕組みをGo言語ではゴルーチンと呼びます。
Goroutine は Go ランタイムによって管理される、Go 言語の軽量スレッド実装です。 Go プログラムは、ゴルーチン内のタスクを合理的に各 CPU にインテリジェントに分散します。
Go プログラムはメイン パッケージの main() 関数から開始され、プログラムが開始されると、Go プログラムは main() 関数のデフォルトの goroutine を作成します。
通常の関数を使用してゴルーチンを作成する
Goキーワードは、Go プログラムで関数のゴルーチンを作成するために使用されます。関数は複数のゴルーチンを作成できますが、ゴルーチンは関数に対応している必要があります。
1) フォーマット
通常の関数のゴルーチンの作成は次のように記述されます。
go 関数名( パラメータ リスト )
- 関数名: 呼び出される関数の名前。
- パラメータ リスト: 関数を呼び出すために渡す必要があるパラメータ。
go キーワードを使用して goroutine を作成する場合、呼び出された関数の戻り値は無視されます。
goroutineでデータを返す必要がある場合は、後述するチャネル(channel)機能を利用し、goroutineからのデータを戻り値としてチャネル経由で渡してください。
2) 例
go キーワードを使用すると、running() 関数が同時に実行され、カウンターが 1 秒ごとに出力されます。一方、メインの goroutine はユーザー入力を待機し、2 つの動作を同時に実行できます。次のコードを参照してください。
package main
import (
"fmt"
"time"
)
func running() {
var times int
// 無限ループの構築
for {
times++
fmt.Println("tick", times)
// 1秒間遅延
time.Sleep(time.Second)
}
}
func main() {
// プログラムを並行実行する
go running()
// コマンドライン入力を受け取り、何もしない
var input string
fmt.Scanln(&input)
}
コマンドラインの出力は次のとおりです。
tick 1
tick 2
tick 3
tick 4
tick 5
コードが実行されると、コマンド ラインは継続的にティックを出力し、同時に fmt.Scanln() を使用してユーザー入力を受け入れることができます。両方の段階を同時に実行できます。
コードの説明は次のとおりです。
12 行目では、for を使用して無限ループを形成しています。
13 行目では、times 変数がループ内で継続的にインクリメントされます。
14 行目は、times 変数の値を出力します。
17 行目で、time.Sleep を使用して 1 秒間一時停止し、ループを継続します。
25 行目では、 go キーワードを使用して running() 関数が同時に実行されます。
29 行目では Enter キーが押されるまでユーザー入力を受け付け、入力内容を入力変数に書き込んで戻り、プログラム全体が終了します。
この例では、Go プログラムが開始されると、ランタイムはデフォルトで main() 関数の goroutine を作成します。 main() 関数の goroutine 内で go running ステートメントが実行されると、running() 関数に属する goroutine が作成され、running() 関数は独自の goroutine 内で実行を開始します。この時点で、main() は実行を続け、2 つのゴルーチンは Go プログラムのスケジューリング メカニズムを通じて同時に実行されます。
無名関数を使用してゴルーチンを作成する
goroutines は、go キーワードの後に匿名関数またはクロージャに対して開始することもできます。
1) 無名関数を使用したゴルーチンの作成形式
無名関数やクロージャを使用してゴルーチンを作成する場合は、go の後に関数定義部分を記述するだけでなく、無名関数の呼び出しパラメータも追加する必要があります。
go func( パラメータリスト ){
関数本体
}( 呼び出しパラメータのリスト)
の:
- パラメータリスト: 関数本体内のパラメータ変数のリスト。
- 関数本体: 無名関数のコード。
- 呼び出しパラメータのリスト: goroutine を開始するときに、無名関数に渡す必要がある呼び出しパラメータ。
2) 無名関数を使用したゴルーチンの作成例
main() 関数内に匿名関数を作成し、その匿名関数のゴルーチンを開始します。匿名関数にはパラメータがありません。コードは、時間指定された印刷カウントの効果と並行して実行されます。以下のコードを参照してください。
package main
import (
"fmt"
"time"
)
func main(){
go func(){
var times int
for {
times++
fmt.Println("tick", times)
time.Sleep(time.Second)
}
}()
var input string
fmt.Scanln(&input)
}
コードの説明は次のとおりです。
- 10 行目では、 go の後にゴルーチンを開始する匿名関数が続きます。
- 12 行目から 19 行目までのロジックは、前のプログラムの running() 関数と一致しています。
- 21 行目の括弧の機能は、匿名関数のパラメータ リストを呼び出すことです。 10 行目の無名関数にはパラメータがないため、21 行目のパラメータ リストも空です。
ヒント
main() 関数が終了すると、すべてのゴルーチンが同時に終了します。
goroutine はスレッドの概念に似ていますが、スケジューリング性能の点ではスレッドほど詳細ではなく、詳細の程度は Go プログラムの goroutine スケジューラの実装と動作環境に依存します。
ゴルーチンを終了する最良の方法は、ゴルーチンに対応する関数に自然に戻ることです。 golang.org/x/net/context パッケージを goroutine のライフタイム深度制御に使用することは可能ですが、このアプローチはまだ実験段階であり、公式に推奨される機能ではありません。
Go バージョン 1.9 の時点では、Goroutine の ID を取得するための標準インターフェイスはありません。