7

アトミック変数が取得と解放のペアで古い値をロードできるかどうかを考えています。アトミック変数 x があり、その変数を解放セマンティクスで格納し、後で取得セマンティクスでロードするとします。理論上、古い値を読み取ることは可能ですか?

std::atomic<int> x = 0;

void thread_1()
{
   x.store(1, std::memory_order_release);
}
void thread_2()
{
   assert(x.load(std::memory_order_acquire) != 0);
}

スレッド 2 が x をロードしたときに関数スレッド 1 が終了した場合 (したがって、新しい値が格納されます)、スレッド 2 が x から古い値をロードすることは可能ですか? 言い換えれば、ロードの前に x への実際のストアが行われた場合、アサートが発生する可能性はありますか?

インターネットの記事から理解した限りでは可能ですが、その理由はわかりません。x へのストアによって生成されるメモリ フェンスはストア バッファを空にすることを保証しますが、x からのロードでのメモリ フェンスの取得はキャッシュ ラインを無効にすることが保証されるため、最新の値を読み取る必要があります。

追加した

acquire-release 自体には強制的な順序付けがないということですか? リリース前に行われたことはすべてリリース前に行われ、取得後に行われたことはすべてその後に行われるため、取得とリリースのペアは他の操作で順序付けを強制します (なぜ??)。私はそれを正しく理解しましたか?コードでは、次のアサートが発火しないことが保証されているということですか

std::atomic<int> x = 0;
std::atomic<int> y = 0;

void thread_1()
{
   y.store(1, std::memory_order_relaxed);
   x.store(1, std::memory_order_release);
}
void thread_2()
{
   x.load(std::memory_order_acquire);
   assert(y.load(std::memory_order_relaxed) != 0);
}

もちろん、スレッド1がすでにストアを終了している場合も同様です。x.load を while (x.load() == 0) に置き換えると、これは 100% 機能しますが、これが機能する原因はわかりません。

そして、コードを次のコードに置き換えるとどうなりますか

std::atomic<int> x = 0;

void thread_1()
{
   x.exchange(1, std::memory_order_acq_rel);
}
void thread_2()
{
   assert(x.exchange(0, std::memory_order_acq_rel) != 0);
}

それは何かを変えますか?

ありがとう。

4

1 に答える 1

5

次の疑似コードとして、メモリの解放/取得順序を使用したスト​​ア/ロード関数を考えることができます。

template<class T>
struct weak_atomic
{
   void store(T newValue)
   {
      ReleaseBarrier();
      m_value = newValue;
   }

   T load()
   {
      T value = m_value;
      AcquireBarrier();
      return value;      
   }

   volatile T m_value;
}

あなたが言った

store to x によって生成されるメモリ フェンスは、ストア バッファーを空にすることを保証します。

私が理解しているように、リリース メモリ バリアによって CPU はストア バッファをフラッシュしますが、これはx に新しい値を適用する前に行われます。そのため、別の CPU で x から古い値を読み取ることができるようです。

とにかく、弱いアトミックは非常に複雑な領域です。ロックフリー プログラミングに進む前に、メモリ バリアを理解していることを確認してください。

追加した

あなたはまだメモリバリアと混同しているようです。これは、その使用法のかなり一般的な例です。

volatile int  x;
volatile bool ok;

void thread_1()
{
   x = 100;
   ok = true;
}

void thread_2()
{
   if (ok)
   {
      assert(x == 100);
   }
}

順不同の実行により、次のシーケンスが得られる場合があります。

thread 1 sets ok to true
thread 2 checks ok is true and reads some garbage from x
thread 1 sets x to 100 but it is too late

別の可能なシーケンス:

thread 2 reads some garbage from x
thread 2 checks for ok value

リリースでそれを修正し、メモリバリアを取得する可能性があります。

volatile int  x;
volatile bool ok;

void thread_1()
{
   x = 100;
   ReleaseBarrier();
   ok = true;
}

void thread_2()
{
   if (ok)
   {
      AcquireBarrier();
      assert(x == 100);
   }
}

ReleaseBarrier()メモリ書き込みがバリアを飛び越えることができないことを保証します。これは、有効な値が既に含まれている場合にokのみ設定されることを意味します。truex

AcquireBarrier()メモリの読み取りがバリアを飛び越えることができないことを保証します。の値は、状態xを確認した後にのみ読み取られることを意味しますok

これが、リリース/取得ペアの使用方法です。この例を my で書き直すことができますweak_atomic

volatile int  x;
weak_atomic<bool> ok;

void thread_1()
{
   x = 100;
   ok.store(true);
}

void thread_2()
{
   if (ok.load())
   {
      assert(x == 100);
   }
}
于 2010-12-14T19:48:40.513 に答える