2009-07-09 2 views
8

私は主にC++の背景から来ていますが、この質問はどの言語のスレッドにも当てはまります。ここでのシナリオは次のとおりスレッドシステムは、共有データが異なるCPUによってキャッシュされることにどのように対処しますか?

  1. 我々は、共有メモリ内の2つのスレッド(スレッドA及びThreadB)、およびx値

  2. はXへのアクセスが適切ミューテックス(または他の適切な同期制御によって制御されているものと有します)

  3. スレッドが異なるプロセッサ上で実行される場合、ThreadAが書き込み操作を実行するが、そのプロセッサは結果をメインメモリではなくL2キャッシュに配置するとどうなりますか?次に、ThreadBが値を読み込もうとすると、それ自身のL1/L2キャッシュ/メインメモリを調べて、そこにあった古い値で動作しますか?

この場合、この問題はどのように管理されますか?

その場合、何ができるのですか?

答えて

9

あなたの例はうまくいくはずです。

複数のプロセッサは、MESIのようにcoherency protocolを使用して、データがキャッシュ間で同期した状態を維持するようにします。 MESIでは、各キャッシュラインは変更され、排他的に保持され、CPU間で共有されるか、無効であるとみなされます。プロセッサ間で共有されるキャッシュラインを書き込むことは、他のCPUのキャッシュラインを無効にして、キャッシュを同期させた状態にします。

しかし、これでは十分ではありません。異なるプロセッサは異なるmemory modelsを持ち、ほとんどの最新のプロセッサはあるレベルのメモリアクセスの再順序付けをサポートしています。これらの場合、memory barriersが必要です。

例えば

あなたはスレッドAがある場合:

DoWork(); 
workDone = true; 

とスレッドB:両方の別々のプロセッサ上で実行されていると

while (!workDone) {} 
DoSomethingWithResults() 

が、書き込みは(DoWork内で行わという保証はありません)しますworkDoneおよびDoSomethingWithResults()への書込みが行われる前にスレッドBから見えるようにして、潜在的に一貫性のない状態を続行します。メモリバリアは読み書きの順序を保証します。スレッドAのDoWork()の後にメモリバリアを追加すると、スレッドBが一貫したビューを取得できるように、DoWorkによって行われたすべての読み書きが強制的にwriteDoneに書き込まれます。本質的にミューテックスはメモリバリアを提供するため、読み書きはロックとロック解除の呼び出しを渡すことができません。

あなたのケースでは、1つのプロセッサが他のプロセッサにキャッシュラインをダーティにしたことを通知し、他のプロセッサにメモリからのリロードを強制します。値を読み書きするためにミューテックスを取得することで、メモリへの変更が予想どおりに他のプロセッサに見えることが保証されます。

+0

この応答はありがとうございます。私は言語/コンパイラレベルで何が達成できるのかについて実用的な限界があるように見えるので、ハードウェアレベルのメカニズムのいくつかがここに現れなければならないのか疑問に思っていました。 – csj

1

mutexのようなほとんどのロックプリミティブはmemory barriersを意味します。これらは強制的にキャッシュのフラッシュとリロードを発生させます。一般に

例えば、

ThreadA { 
    x = 5;   // probably writes to cache 
    unlock mutex; // forcibly writes local CPU cache to global memory 
} 
ThreadB { 
    lock mutex; // discards data in local cache 
    y = x;   // x must read from global memory 
} 
+1

私はバリアがキャッシュのフラッシュを強制するとは思わない、彼らはメモリ操作の順序に制約を強制します。 Xへの書き込みがmutexのロック解除を渡すことができる場合、キャッシュフラッシュは役に立ちません。 – Michael

+0

コンパイラがそれらのメモリ操作を再注文すると、障壁はかなり無駄になるでしょうか?少なくともGCCの場合、これは通常、メモリクローバーで実装されていると思います。これは、GCCに「メモリに関するすべての仮定を無効にする」と伝えます。 – ephemient

+0

ああ、あなたが言っていることが分かります。順序がプロセッサ間で適切に尊重されている限り、キャッシュフラッシュは必要ありません。ですから、この説明は単純化されたものであり、ハードウェアの詳細を詳しく解説しています。 – ephemient

0

、コンパイラは、共有メモリを理解し、共有メモリを共有可能な所定の位置に配置されることを保証するためにかなりの努力を要します。現代のコンパイラは、操作とメモリアクセスを注文する方法が非常に複雑です。スレッディングと共有メモリの性質を理解する傾向があります。これは完璧だと言っているわけではありませんが、一般的に、関心のある部分はコンパイラによって処理されます。

0

C#には、この種の問題をサポートするビルドがいくつかあります。 変数をvolatileキーワードでマークすると、すべてのCPU上でその変数を強制的に同期させることができます。

public static volatile int loggedUsers; 

他の部分は、.NET Threading.Monitor.Enter(X)と呼ばれる方法であり、xがロックする変数であるThreading.Monitor.Exit(x)は、周りの構文wrappperあります。これにより、ロックスレッドがExit(x)を呼び出すまでxをロックしようとする他のスレッドが待機する必要があります。

public list users; 
// In some function: 
System.Threading.Monitor.Enter(users); 
try { 
    // do something with users 
} 
finally { 
    System.Threading.Monitor.Exit(users); 
} 
関連する問題