web-dev-qa-db-ja.com

代入演算子 '='はアトミックですか?

グローバル変数を使用してスレッド間通信を実装しています。

//global var
volatile bool is_true = true;

//thread 1
void thread_1()
{
    while(1){
        int rint = Rand() % 10;
        if(is_true) {
            cout << "thread_1: "<< rint <<endl;  //thread_1 prints some stuff
            if(rint == 3)
                is_true = false;  //here, tells thread_2 to start printing stuff
        }
    }
}

//thread 2
void thread_2()
{
    while(1){
        int rint = Rand() % 10;
        if(! is_true) {  //if is_true == false
            cout << "thread_1: "<< rint <<endl;  //thread_2 prints some stuff
            if(rint == 7)  //7
                is_true = true;  //here, tells thread_1 to start printing stuff
        }
    }
}

int main()
{
    HANDLE t1 = CreateThread(0,0, thread_1, 0,0,0);
    HANDLE t2 = CreateThread(0,0, thread_2, 0,0,0);
    Sleep(9999999);
    return 0;
}

質問

上記のコードでは、グローバル変数volatile bool is_trueを使用して、thread_1とthread_2の間で印刷を切り替えています。

ここで代入演算を使用してもスレッドセーフかどうか

25
Alcott

Win32は、適切に配置された4バイトおよびポインターサイズの値に対してのみアトミック性を保証するため、このコードはWin32でスレッドセーフであることが保証されていません。 boolは、これらのタイプの1つであるとは限りません。 (通常は1バイトタイプです。)

これがどのように失敗する可能性があるかの実際の例を要求する人のために:

boolが1バイト型であると仮定します。また、is_true変数が別のbool変数(other_boolと呼びましょう)に隣接して格納されているため、両方が同じ4バイトの行を共有するとします。具体的には、is_trueがアドレス0x1000にあり、other_boolがアドレス0x1001にあるとします。両方の値が最初はfalseであり、あるスレッドがis_trueを更新すると同時に、別のスレッドがother_boolを更新しようとするとします。次の一連の操作が発生する可能性があります。

  • スレッド1は、is_trueis_trueを含む4バイトの値をロードすることにより、other_booltrueに設定する準備をします。スレッド1は0x00000000を読み取ります。
  • スレッド2は、other_boolis_trueを含む4バイトの値をロードすることにより、other_booltrueに設定する準備をします。スレッド2は0x00000000を読み取ります。
  • スレッド1は、is_trueに対応する4バイト値のバイトを更新し、0x00000001を生成します。
  • スレッド2は、other_boolに対応する4バイト値のバイトを更新し、0x00000100を生成します。
  • スレッド1は、更新された値をメモリに格納します。 is_truetrueになり、other_boolfalseになりました。
  • スレッド2は、更新された値をメモリに格納します。 is_truefalseになり、other_booltrueになりました。

このシーケンスの最後に、is_trueの古い値をキャプチャしたスレッド2によって上書きされたため、is_trueへの更新が失われたことに注意してください。

X86はバイト単位の更新をサポートし、非常にタイトなメモリモデルを備えているため、このタイプのエラーを非常に許容します。他のWin32プロセッサはそれほど寛容ではありません。たとえば、RISCチップはバイト単位の更新をサポートしていないことが多く、サポートしている場合でも、通常は非常に弱いメモリモデルを備えています。

61
Raymond Chen

いいえ、そうではありません.....ある種のロッキングプリミティブを使用する必要があります。プラットフォームに応じて、ブーストを使用するか、ネイティブウィンドウを使用する場合はInterlockedCompareExchangeなどを使用できます。

実際、あなたの状況では、スレッドセーフなイベントメカニズムのいくつかを使用して、他のスレッドに「シグナル」を送信して、必要なことを開始できるようにすることができます。

7
Keith Nicholas

最近のすべてのプロセッサでは、自然に整列されたネイティブタイプの読み取りと書き込みはアトミックであると想定できます。メモリバスが少なくとも読み取りまたは書き込み中のタイプと同じ幅である限り、CPUはこれらのタイプを1回のバストランザクションで読み書きするため、他のスレッドがそれらを半分完了した状態で見ることはできません。 x86およびx64では、読み取りと書き込みが保証されません大きい 8バイトよりもアトミックです。これは、ストリーミングSIMD拡張命令(SSE)レジスタの16バイトの読み取りと書き込み、および文字列操作がアトミックではない可能性があることを意味します。

自然に整列されていないタイプの読み取りと書き込み(たとえば、4バイトの境界を越えるDWORDの書き込み)は、アトミックであることが保証されていません。 CPUは、これらの読み取りと書き込みを複数のバストランザクションとして実行する必要がある場合があります。これにより、別のスレッドが読み取りまたは書き込みの途中でデータを変更または表示できるようになる可能性があります。

4
themadme