2つのスレッドの物語

IT企業とのインタビューで、次の質問に答えることが提案されました。



チャレンジ。 次のコードを考えます:



		 static int counter = 0;

		 void worker()
		 {
			 for(int i = 1; i <= 10; i ++)
				カウンター++;
		 }




worker()



プロシージャは2つのスレッドから開始されます。 counter



変数には、両方のスレッドの最後にどのような値が含まれますか?



ちょっとした理論。 スレッドは、同じプロセス内の並列実行タスクです。 プロセスとスレッドの主な違いは、1つのプロセスのすべてのスレッドがそのプロセスの共通ア​​ドレス空間で動作することです。



実行スレッドthread )とデータストリーム( stream )を混同しないように、スレッドスレッドを呼び出しません。







回答番号1バリエーション。 counter



変数には20という数値が含まれます。これは最も望ましい結果ですが、最も誤った回答でもあります。



問題は、このコードが同期解除から保護されていないことです。 各スレッドは互いに独立して動作し、一般データ( counter



変数)が他の誰かによって変更される可能性があるとは考えていません。 したがって、より正しい答えはこれです。 これは安全でないコードであり、 counter



変数の値はundefinedになり
ます。



オプション番号2。プロセッサ、OS、およびコンパイラの基本をまだ理解しているので、どの結果が得られるかをより正確に推測できます。



worker()



ルーチンは非常に単純です。 したがって、最適化の段階で、コンパイラはそのような構成でforループを展開できます。



	カウンター+ = 10; 




これでコードは非常に単純なため、2番目のスレッドが開始する前であっても、最初のスレッドは実行を終了する時間ができます。 したがって、20という数字が得られます。



そのような場合も可能です。 スレッドは、 counter



変数(ゼロ)の値をRAMから読み取り、プロセッサーのレジスターに入れます。 各スレッドは独自のプロセッサコンテキストで動作するため、各スレッドには独自のレジスタセットがあります。 したがって、コンテキスト内の最初のスレッドは、レジスタの値を0から10に増やしてから、結果をメモリに戻します。 そして、2番目のスレッドは同じことを行い、10個のスレッドを書き換えます。 その結果、10という数字が得られます。



10と20の値は実際には実際に最も可能性が高いですが、これはまだ完全な答えではありません。



オプション番号3。潜在的に実際の、しかし最大限に効果のない作業スキームを想像してみましょう。 したがって、サイクルの各反復は、3つのアトミックアクションを等しく繰り返します。つまり、 counter



値をプロセッサレジスタに読み込み、このレジスタをインクリメントし、レジスタから新しい値をメモリに書き戻します。 (ループは何の役割も果たさないため、ループ自体の実装は考慮しません。)さらに、スレッド実行コンテキストの切り替えは、必要なときに行われます。 このような条件下では、2〜20の結果が得られます。



次に、表形式のタイムラインで、デュースを取得する可能性を検討します。 ステップ0は初期状態です。 「→ n 」、「 n +」、「 n →」の各操作は、スレッド番号nのコンテキストでメモリからレジスタへの読み取り、レジスタのインクリメント、およびレジスタの値のメモリへの書き込みをそれぞれ表します。 行「ループ」は反復回数を示します。 知覚を改善するために、現在のステップの値の変更が強調表示されます。



ステップ 0 1 2 3 4 5 ... 27 28 29日 30 31 32 33 34 35 ... 57 58 59 60
運営 - →1 1+ →2 2+ 2→ →2 2+ 2→ 1→ →2 2+ →1 1+ 1→ →1 1+ 1→ 2→
スレッド番号1 登録する - 0 1 1 1 1 1 1 1 1 1 1 1 2 2 ... 9 10 10 10
サイクル 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 10 10 10 10
スレッド番号2 登録する - - - 0 1 1 ... 8 9 9 9 1 2 2 2 2 2 2 2 2
サイクル 1 1 1 1 1 1 9 9 9 9 10 10 10 10 10 10 10 10 10
記憶 0 0 0 0 0 1 8 8 9 1 1 1 1 1 2 9 9 10 2


(追加:読者の要求に応じて、ビットマップ付きのテーブルのバリアントがあります 。)



ステップ1〜2:最初のスレッドがレジスタ0に入り、1増加します。 3-29:制御は2番目のスレッドに転送されます。これもゼロを読み取り、9回の完全な反復を実行します。 30:最初のスレッドはループの最初の反復を完了し、カウントされたユニットをメモリに保存します(9個をラビングします)。 31–32:2番目のスレッドが最後の(10番目の)反復を開始し、ユニットを読み取り、無害にします。 33–59:最初のスレッドは残りの9回の反復を完全に実行し、作業を終了します。 60:2番目のスレッドは、カウントされた2つをメモリに書き込み、作業を終了します。



_________

NB:これは私のブログからのクロスポストです。 Habréの視聴者が私のブログよりも多いため、私はそれを再版しました(:そして、素材は興味深いと思います。



All Articles