例を見てください
パッケージメイン インポート( "FMT" "ランタイム" "同期" ) 主FUNC(){ runtime.GOMAXPROCS(1) = sync.WaitGroup {}:WG wg.Add(20) iについて:= 0; I <10; I ++ { FUNC(){行く fmt.Println( "ルーチン1 I行く"、I) wg.Done() }() } iについての:= 0; I <10; 私は{++ FUNCを行く(私はint型){ :fmt.Println(I "行くルーチン2 I") wg.Done() }(I) } wg.Wait() }
出力:
ルーチン2 iを行く:9 10:1 iをルーチンに行く 私は、ルーチン1を行く:10は 私がルーチン1を行く:10は 私がルーチン1を行く:10は 私がルーチン1を行く:10は 私がルーチン1を行く:10は 私がルーチン1を行く:10には、 ルーチンを行きます1 I:10 行くルーチン1、I:10 行くルーチン1 I:10 行くルーチン2 I:0は 1:2ルーチンiが行く 2:2私はルーチン行く 2 iはルーチン行く:3 ルーチン行く2 iが4 ルーチン2 I行きます:5 ルーチン行く2私は:6 7:私は2ルーチン行く 行くルーチン2 I:8
なぜ、この結果は?
並行処理は、並列に等しくありません
golangコア開発者ロブ・パイクは特にこのトピックに言及した(これを見ることができます興味を持っているビデオやオリジナルの参照PPTを)
私たちはループのために行くゴルーチンを作成するために使用しているが、我々は当然のそれを取る各サイクルの変数を考えて、golangこのゴルーチンを行いますが、その時は出力変数です。この時点で、私たちは、考え方に陥ります。同時に並列のデフォルト。
確かに、コードを機能されるの同時実行の創出を通過ゴルーチン。私たちが考えているようしかし、それは、各サイクル時間に応じてそれを実行しますか?答えはノーです!
ロブ・パイクは、具体的に同時golangが機能コードで特定の構造を参照して述べたロジックを同時に実行することができるが、物理的に同時に実行されなくてもよいです。物理レベルで平行にいう同一または異なるタスクの異なるCPUを用いて行われます。
ゴルーチンスケジューリングモデルのgolang各ゴルーチンは、仮想CPUで実行されているため、判断(つまり、私たちは(1)runtime.GOMAXPROCSによって設定された仮想CPUの数である)です。仮想CPUの数は、CPUの実際の数と一致しない場合があります。各ゴルーチンには、特定のP(仮想CPU)は、彼がゴルーチンの有効Pは、Pの実行を選ぶに戻り、すべての時間を維持するために選択され、M(物理的なコンピューティングリソース)されます。
各Pはゴルーチンスタック情報、情報などが実行可能であるなど、キューにそのゴルーチンGに維持されます。デフォルトでは、数Pは、実際の物理CPUに等しいです。私たちはループをゴルーチンを作成するときに、それは各ゴルーチンP異なるキューに割り当てられます。Mの数はユニークなものではありませんがM Pは、それだけでランダムにゴルーチンを選択した可能性がある場合、ランダムに選択されました。
この問題では、P = 1を設定します。だから、すべてのゴルーチンはで同じPにバインドされます。我々は値runtime.GOMAXPROCSを変更する場合は、別の順序が表示されます。我々出力ゴルーチンIDの場合は、ランダムに選択さの効果を見ることができます:
パッケージメイン インポート( "FMT" "ランタイム" "のStrConv" "文字列" "同期" ) 主FUNC(){ = sync.WaitGroup {}:WG wg.Add(20) iについて:= 0; I <10; 私は++ { FUNCを行く(){ VARのBUF [64]バイト N:= runtime.Stack(BUF [:]、false)を idField:= strings.Fields(strings.TrimPrefix(文字列(のbuf [:n]は)、 "ゴルーチン" ))[0] ID、ERR = strconv.Atoi(idField) ERR =ゼロ{場合! パニック(fmt.Sprintfは( "ゴルーチンIDを取得することはできません:%Vを"、ERR)) } fmt.Printf(「1ルーチン行きます:I - %D id--%D \ n」は、I、ID) WG。 ()行わ}() } iについて:= 0; I <10; I ++ { FUNCを(私はint型)行く{ VaRのBUF [64]バイト N:= runtime.Stack(BUF [:]、偽) idField:= strings.Fields(strings.TrimPrefix(ストリング(BUF [:N])、 "ゴルーチン"))[0] のID、 = strconv.Atoi(idField):ERR !誤る場合= nilの{ パニック(fmt.Sprintfは( "ゴルーチンIDを取得することはできません:%vを"、ERR)) } %d個のID - I:fmt.Printf(「ルーチン2を行きます- %Dを\ n」、I、ID) wg.Done() }(I) } wg.Wait() }
出力:
ルーチン1を行く:I - 10 id-- 20は、 ルーチン1を行く:I - 10 id-- 19は、 ルーチン1を行く:I - 10 id-- 22は、 ルーチン1を行く:I - 10 id-- 25 ゴールーチン1:I - 10 id-- 28 行くルーチン2:I - 8 id-- 37 行くルーチン1:I - 10 id-- 23 行くルーチン1:I - 10 id-- 21は ルーチン行く2:私は- 0 id-- 29は、 移動ルーチン2:I - 9 id-- 38は ルーチン行く2:I - 1 id-- 30は、 ルーチン行く2:I - 5 id-- 34は ルーチン行く2:I - - 2 id-- 31は、 移動ルーチン2:I - 6 id-- 35 行くルーチン2:I - 3 id-- 32 行くルーチン1:I - 10 id-- 24 行くルーチン2:I - 7 id-- 36は ルーチン1行く:私は- 10 id-- 27 ルーチン2を行く:I - 4 id-- 33 ルーチン1を行く:I - 10 id-- 26
サイクルでゴルーチンの定義で行くが、私たちは、この質問に戻ります。しかし、我々が言った、と同時並行に等しくありません。そのため、定義が、今では必ずしもそれを行うことはできません。M待った後ゴルーチンを実行するために、Pを選択します。ゴルーチンでgolangが予定されている方法について(GPMモデル)、あなたが参照できるスケーラブルゴースケジューラの設計ドキュメントやLearnConcurrency
この時点で、あなたは、第1の出力goroutine2は、出力がそれをgoroutine1理由を理解することができるはずです。
ここでは、出力になぜgoroutine1を説明するために10です。
変数をバインドゴルーチン方法
forループgolangにおいて、golangそれぞれ同じインスタンス変数(I用いるすなわち、タイトル)を使用します。そしてgolang間する環境変数が共有です。
このゴルーチンへのディスパッチャは、それが直接保存変数のアドレスを読み取ると、この時間は、問題があるでしょう:変数が変更される可能性が高いので、ゴルーチンは、唯一の変数のアドレスを保存しました。
変数のアドレスを使用して、各時間が同じであるため、ループ内の問題と組み合わせることで、それは私変更するたびに言うことです、サイクルの終わりで、私は10になり、また、私は唯一のゴルーチンを保存しましたgoroutine1が行われたときのメモリアドレスだけなので、私は、どのくらいを読み出す置くことを躊躇しませんか?10!
しかし、なぜ10それはないgoroutine2?
逆にgoroutine2、理解しやすいです。各ループは、新しい変数を再生成し、各ゴルーチンを保存しているため、各新しい変数のアドレスです。これらの変数間の相互干渉は、誰もが改ざんされることはありません。したがって、出力は、0からであろう場合 - 9順次出力されます。