1

フェンスが実際にコードの同期を強制する方法を理解するのに苦労しています。

たとえば、このコードがあるとします

bool x = false;
std::atomic<bool> y;
std::atomic<int> z;
void write_x_then_y()
{
    x = true;
    std::atomic_thread_fence(std::memory_order_release);
    y.store(true, std::memory_order_relaxed);
}
void read_y_then_x()
{
    while (!y.load(std::memory_order_relaxed));
    std::atomic_thread_fence(std::memory_order_acquire);
    if (x)
        ++z;
}
int main()
{
    x = false;
    y = false;
    z = 0;
    std::thread a(write_x_then_y);
    std::thread b(read_y_then_x);
    a.join();
    b.join();
    assert(z.load() != 0);
}

リリース フェンスの後にアトミック ストア操作が続き、取得フェンスの前にアトミック ロードがあるため、すべてが想定どおりに同期され、アサートは発生しません。

しかし、yがこのようなアトミック変数ではなかった場合

bool x;
bool y;
std::atomic<int> z;
void write_x_then_y()
{
    x = true;
    std::atomic_thread_fence(std::memory_order_release);
    y = true;
}
void read_y_then_x()
{
    while (!y);
    std::atomic_thread_fence(std::memory_order_acquire);
    if (x)
        ++z;
}

その場合、データ競合が発生する可能性があると聞きました。しかし、それはなぜですか?コードを適切に同期させるために、リリース フェンスの後にアトミック ストアが続き、取得フェンスの前にアトミック ロードが必要なのはなぜですか?

また、データ競合によってアサートが発生する実行シナリオを誰かが提供できれば幸いです

4

1 に答える 1

2

2 番目のスニペットでは、実際のデータ競合は問題になりません。このスニペットは問題ありません...コンパイラーが、書かれたものから文字通りマシンコードを生成する場合。

ただし、コンパイラは、シングルスレッド プログラムの場合の元のコードと同等のマシン コードを自由に生成できます。

たとえば、コンパイラは変数がループy内で変更されないことに注意while(!y)できるため、この変数を一度ロードして登録し、次の反復でそのレジスタのみを使用できます。したがって、最初はy=false無限ループになります。

可能性のある別の最適化は、while(!y)ループを削除することです。これは、揮発性変数またはアトミック変数へのアクセスが含まれておらず、同期アクションを使用していないためです。(C++ 標準では、正しいプログラムは最終的に上記のアクションのいずれかを実行する必要があるため、コンパイラはプログラムを最適化するときにその事実に依存する場合があります)。

等々。

より一般的には、C++ 標準は、非アトミック変数への同時アクセスがUndefined Behaviorにつながることを指定しています。これは、「保証がクリアされた」のようなものです。そのため、アトミック変数を使用する必要があります。 y

一方、変数xへのアクセスはメモリ フェンスのために同時実行されないため、変数はアトミックである必要はありません。

于 2015-12-09T23:34:41.620 に答える