2013-08-02 15 views
6

CまたはC++プログラムでは、2つのスレッドが同じグローバル変数を使用する場合は、mutexを介してvarをロックする必要があります。この場合、同時アクセスから変数をロックする必要がありますか?

しかし、正確にはどのような場合ですか?

  1. スレッド1:スレッド2をお読みください。スレッド2を書く:スレッド2を書く:
  2. スレッド1を読ん
  3. スレッド1を読んもちろん

を書くあなたはケースにロックする必要があります3しかし、他の2つのケースとは何ですか?ケース2(非アトミック操作)ではどうなりますか?何らかの種類のアクセス違反があるのですか、またはスレッド2はちょうど古い値を取得していますか?私はこれについて混乱しています。なぜなら、ハードウェアレベルのメモリとレジスタは同時に(通常のPCハードウェアでは)アクセスできないか、パラレルバスラインを持つ並列CPUを並列RAMチップに持っているからですか?

+11

ケース2および3、すなわち、少なくとも1つのスレッドが少なくとも1つの他のスレッドの読み取りまたは書き込みによって書き込んでいる状況。 – juanchopanza

+0

異なるスレッドのデータにアクセスするときに常にロックする必要はありません。アトミック操作、特にC++ 11の 'std :: atomic'を見てください。 – nijansen

+0

そして、最後の文に対処するために、マルチコアCPU上で、さまざまなレベルのキャッシュにそれぞれの値のコピーが複数存在することがあります。 – Hulk

答えて

8

それぞれの場合に起こりうることを考えてください。レースの状態だけを考えてみましょう。それは簡単で、結果を見るだけで十分です。

ケース1では、変数が変更されていないため、順序がどちらであっても、両方のスレッドが同じ値を読み取ります。だから基本的に、ここで何も間違っていません。

ケース2と3は悪化します。競合状態があり、どのスレッドが先にアクセスするかわからないとします。つまり:

ケース2の場合:すべての操作の最後にある変数の値はきれいです(スレッド1によって書き込まれる値になります)。しかし、スレッド2は変数の古い値を取得する可能性があります。クラッシュやその他の問題を引き起こす可能性があります。

ケース3の場合:最後に書き込み操作を実行するスレッドに依存するため、変数の終了値は予測できません。

ケース2と3の場合、スレッドの1つが不整合な状態で変数にアクセスしようとし、スレッドの1つで読み取られたゴミデータで終了する可能性があります。ケース2)、またはすべての操作の完了後に変数内のデータを捨てることさえできます。

だからいや、ロック用ケース2のロックと3

+3

ケース2はかなり悪い可能性があります:スレッド2で読み取ると、変数が不整合な状態になる可能性があります。 – juanchopanza

+0

@juanchopanza、それは本当です(ケース3の場合も同様です)。しかし、答えを複雑にしないように言及しませんでした。私はそれを答えに加えなければなりませんか、それともそのままのままにしておきますか? – SingerOfTheFall

+4

これを答えに追加する必要があると思います。非アトミック操作の主な問題です。あなたの答えは原子性を暗示しているようです。また、ロックによって特定のアクセス順序が保証されるわけではありません。 – juanchopanza

3

ルールは単純です:あなたは、同時に別のスレッドがアクセスする変数に書き込む場合

、その後、あなたが適切な順序付けを必要とします操作(例えば、ロックを介して)。この単純なルールで

、我々は簡単に例ごとに評価することができます

  1. ない書き込みを、必要同期のため
  2. 書き込み、同時アクセスは、同期
  3. 書き込み、同時アクセスを必要としません同期が必要

ロックしないとどうなりますか?

正式には、は未定義の動作です。私たちが知らないという意味。一方、可能な唯一の対応は、問題の程度を把握するのに役立ちます。マシンレベルで

、何が起こることはどちらかである:読み取りの場合

  • :読み取りの場合は、古い値へのアクセス
  • :一部の値へのアクセスは、(半分だけがで更新されますあなたが見て時間)、書き込みの場合は
  • は:最終的な値は、値

のビットレベルでごった煮()である...そして、あなたが間違ったポインタ/サイズを読んでいつでもすることを忘れないようにしましょうそれはcrにつながる可能性があります灰。

コンパイラレベルでは、コンパイラは、と同じようにを最適化することができます。この場合、それは意味があります。 "冗長" 削除

  • 読み取り:if (flag) while (true)while (flag)を変換...
  • "未使用" 削除
  • ...

は、メモリの存在を書き込み、フェンスおよび明示的な同期命令(ミューテックスの使用によって導入される)によってこれらの最適化が妨げられます。

2

ルールは単純である:オブジェクトは、1つのスレッドより によって(読み出しまたは書き込み)アクセスされ、任意のスレッドによって変更される場合、全て が同期する必要がアクセスします。それ以外の場合は、 の動作が定義されていません。

0

2つの場合を除いて、それが見えるかもしれませほど単純ではありません。

  1. リーダーとなしの作家のみが存在する場合、あなたは、同期するためのない必要性を行います。
  2. 複数のスレッドに書き込みがあり、それらのうちの少なくとも1人が読み取り - 変更 - 書き込み操作(たとえば++x;)を実行している場合は、常にを同期する必要があります。他のすべての場合には

、あなたが少なくとも一つの作家とリーダー(またはいくつかの作家を)持っている場合、あなたは通常(非常に少数の例外を除いて)アクセスを同期する必要がありますが、必ずしも常に、および必ずしも厳密に可能な方法ではありません。

これは、必要な保証によって大きく異なります。アプリケーションによっては、スレッドに対して厳密なシーケンシャル整合性が必要なものがあります(また、ロックの公正さが必要な場合もあります)。アプリケーションによっては、同じスレッド内での保証の前に、うまく動作しますが、パフォーマンスが大幅に向上するアプリケーションもあります。しかし、他のアプリケーションでさえもそれほど必要ではなく、リラックスした操作や完全な保証なしでは完全に満足しています。例えば

、ワーカースレッドのこの「典型的な」実装では、作家と読者を持っています

// worker thread 
running = true; 
while(running) { task = pull_task(); execute(task); } 

// main thread exit code 
running = false; 
join(workerthread); 

これは、任意の同期せずに完璧に適しています。確かに、runningの値が変更される時期や方法は変わっていませんが、実際には違いはありません。メモリ位置にある程度の「ランダム」な中間値を持つ方法はなく、ワーカースレッドがタスクを実行中である可能性が最も高いため、変更が数十ナノ秒前またはそれ以降に見えるかどうかは重要ではありません。最悪の場合、それは数ミリ秒後に変化を拾う。最終的に、次の反復で、ワーカースレッドに変更を受け取り、終了します。
Dr. Dobbに掲載されたSPSC早送りキューは、数年前に同様の原則に基づいていました。

多くの異なる同期モードでの良好で包括的な読み取りとその意味は、GCC documentationに記載されています。

関連する問題