グローバル変数を使用してスレッド間通信を実装しています。
//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の間で印刷を切り替えています。
ここで代入演算を使用してもスレッドセーフかどうか?
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
を更新しようとするとします。次の一連の操作が発生する可能性があります。
is_true
とis_true
を含む4バイトの値をロードすることにより、other_bool
をtrue
に設定する準備をします。スレッド1は0x00000000を読み取ります。other_bool
とis_true
を含む4バイトの値をロードすることにより、other_bool
をtrue
に設定する準備をします。スレッド2は0x00000000を読み取ります。is_true
に対応する4バイト値のバイトを更新し、0x00000001を生成します。other_bool
に対応する4バイト値のバイトを更新し、0x00000100を生成します。is_true
はtrue
になり、other_bool
はfalse
になりました。is_true
はfalse
になり、other_bool
はtrue
になりました。このシーケンスの最後に、is_true
の古い値をキャプチャしたスレッド2によって上書きされたため、is_true
への更新が失われたことに注意してください。
X86はバイト単位の更新をサポートし、非常にタイトなメモリモデルを備えているため、このタイプのエラーを非常に許容します。他のWin32プロセッサはそれほど寛容ではありません。たとえば、RISCチップはバイト単位の更新をサポートしていないことが多く、サポートしている場合でも、通常は非常に弱いメモリモデルを備えています。
いいえ、そうではありません.....ある種のロッキングプリミティブを使用する必要があります。プラットフォームに応じて、ブーストを使用するか、ネイティブウィンドウを使用する場合はInterlockedCompareExchangeなどを使用できます。
実際、あなたの状況では、スレッドセーフなイベントメカニズムのいくつかを使用して、他のスレッドに「シグナル」を送信して、必要なことを開始できるようにすることができます。
最近のすべてのプロセッサでは、自然に整列されたネイティブタイプの読み取りと書き込みはアトミックであると想定できます。メモリバスが少なくとも読み取りまたは書き込み中のタイプと同じ幅である限り、CPUはこれらのタイプを1回のバストランザクションで読み書きするため、他のスレッドがそれらを半分完了した状態で見ることはできません。 x86およびx64では、読み取りと書き込みが保証されません大きい 8バイトよりもアトミックです。これは、ストリーミングSIMD拡張命令(SSE)レジスタの16バイトの読み取りと書き込み、および文字列操作がアトミックではない可能性があることを意味します。
自然に整列されていないタイプの読み取りと書き込み(たとえば、4バイトの境界を越えるDWORDの書き込み)は、アトミックであることが保証されていません。 CPUは、これらの読み取りと書き込みを複数のバストランザクションとして実行する必要がある場合があります。これにより、別のスレッドが読み取りまたは書き込みの途中でデータを変更または表示できるようになる可能性があります。