11

2つのスレッドが同じメモリから読み取りを行っていて、そのメモリに書き込みを行っているスレッドがない場合、操作は安全であると理解しています。ただし、一方のスレッドが読み取りを行っており、もう一方のスレッドが書き込みを行っている場合はどうなるかわかりません。どうなるでしょうか?結果は未定義ですか?それとも、読み取りが古くなっているだけでしょうか?失効した読み取りが問題にならない場合は、変数への非同期の読み取り/書き込みを行っても問題ありませんか?または、データが破損している可能性があり、読み取りも書き込みも正しくないため、この場合は常に同期する必要がありますか?

後者の場合、メモリアクセスの競合によって状態が未定義のままになることを学びました...しかし、どこでそれを学んだのか思い出せず、見つけるのに苦労していますグーグルで答える。私の直感では、変数はレジスターで操作され、真の(ハードウェアの場合のように)並行性は不可能(またはそうである)であるため、発生する可能性のある最悪の事態は古いデータ、つまり次のとおりです。

WriteThread: copy value from memory to register
WriteThread: update value in register
ReadThread:  copy value of memory to register
WriteThread: write new value to memory

その時点で、読み取りスレッドには古いデータがあります。

4

3 に答える 3

12

結果は未定義です。破損したデータは完全に可能です。明らかな例として、32ビットプロセッサによって操作されている64ビット値について考えてみます。値が単純なカウンターであると仮定し、下位32ビットに0xffffffffが含まれている場合に値をインクリメントします。増分は0x00000000を生成します。それを検出すると、上位の単語をインクリメントします。ただし、他のスレッドが下位ワードがインクリメントされてから上位ワードがインクリメントされるまでの間に値を読み取った場合、それらはインクリメントされていない上位ワードの値を取得しますが、下位ワードは0に設定されます-値は完全に異なります増分が完了する前または後のいずれかから。

于 2010-08-26T23:24:17.707 に答える
12

通常、メモリは、CPU アーキテクチャによって決定される原子単位で読み書きされます (最近では、32 ビットと 64 ビットの境界に整列された 32 ビットと 64 ビットのアイテムが一般的です)。

この場合、何が起こるかは、書き込まれるデータの量によって異なります。

32 ビットのアトミック読み取り/書き込みセルの場合を考えてみましょう。

2 つのスレッドがこのように整列されたセルに 32 ビットを書き込む場合、何が起こるかは完全に明確に定義されています。2 つの書き込まれた値の 1 つが保持されます。残念なことに、あなた (つまり、プログラム) にとって、どの値が適切かはわかりません。非常に巧妙なプログラミングにより、この読み取りと書き込みの原子性を実際に使用して同期アルゴリズム (たとえば、Dekker のアルゴリズム) を構築できますが、通常は代わりにアーキテクチャで定義されたロックを使用する方が高速です。

2 つのスレッドがアトミック単位よりも多く書き込む場合(たとえば、両方とも 128 ビットの値を書き込む)、実際には、書き込まれた値のアトミック単位サイズの断片は完全に明確に定義された方法で格納されますが、どれがどれかはわかりません。どの値の断片がどの順序で書き込まれるか。そのため、最終的にストレージに格納されるのは、最初のスレッド、2 番目のスレッド、または両方のスレッドの原子単位サイズのビットの混合からの値です。

同様の考え方は、1 つのスレッドが読み取り、1 つのスレッドがアトミック単位以上で書き込みを行う場合にも当てはまります。

基本的に、メモリ位置への非同期の読み取りと書き込みは行いたくありません。アーキテクチャによって非常に明確に定義されている場合でも、結果がわからないためです。

于 2010-08-26T23:29:44.670 に答える
0

Ira Baxterの回答で示唆したように、CPUキャッシュはマルチコアシステムでも役割を果たします。次のテストコードを検討してください。

危険はロビソンになります!

次のコードは、より一貫性のある結果を達成するためにリアルタイムへの優先順位を上げます-これには管理者権限が必要ですが、デュアルコアまたはシングルコアシステムでコードを実行する場合は、テスト実行中にマシンがロックされるため、注意してください。

#include <windows.h>
#include <stdio.h>

const int RUNFOR = 5000;
volatile bool terminating = false;
volatile int value;

static DWORD WINAPI CountErrors(LPVOID parm)
{
    int errors = 0;
    while(!terminating)
    {
        value = (int) parm;
        if(value != (int) parm)
            errors++;
    }
    printf("\tThread %08X: %d errors\n", parm, errors);
    return 0;
}

static void RunTest(int affinity1, int affinity2)
{
    terminating = false;
    DWORD dummy;
    HANDLE t1 = CreateThread(0, 0, CountErrors, (void*)0x1000, CREATE_SUSPENDED, &dummy);
    HANDLE t2 = CreateThread(0, 0, CountErrors, (void*)0x2000, CREATE_SUSPENDED, &dummy);

    SetThreadAffinityMask(t1, affinity1);
    SetThreadAffinityMask(t2, affinity2);
    ResumeThread(t1);
    ResumeThread(t2);

    printf("Running test for %d milliseconds with affinity %d and %d\n", RUNFOR, affinity1, affinity2);
    Sleep(RUNFOR);
    terminating = true;
    Sleep(100); // let threads have a chance of picking up the "terminating" flag.
}

int main()
{
    SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
    RunTest(1, 2);      // core 1 & 2
    RunTest(1, 4);      // core 1 & 3
    RunTest(4, 8);      // core 3 & 4
    RunTest(1, 8);      // core 1 & 4
}

私のクアッドコアIntelQ6600システム(iircには2セットのコアがあり、各セットがL2キャッシュを共有します-とにかく結果を説明します;))では、次の結果が得られます。

アフィニティ1および2で5000ミリ秒のテストを実行
        スレッド00002000:351883エラー
        スレッド00001000:343523エラー
アフィニティ1および4で5000ミリ秒のテストを実行
        スレッド00001000:48073エラー
        スレッド00002000:59813エラー
アフィニティ4および8で5000ミリ秒のテストを実行
        スレッド00002000:337199エラー
        スレッド00001000:335467エラー
アフィニティ1および8で5000ミリ秒のテストを実行
        スレッド00001000:55736エラー
        スレッド00002000:72441エラー
于 2010-08-27T00:33:09.937 に答える