ピーターソンのアルゴリズムは、逐次整合性を必要とする例です。
mutexの前の日に、アルゴリズムを使用して、保護された領域への単一スレッドアクセスを提供しました。 アルゴリズムは2つのスレッドでのみ機能し、それぞれが保護領域にアクセスする意図を表すフラグを管理します。 両方ともフラグが(ほぼ)同じ時刻に設定されている場合、両方ともバックオフして再試行します。 実際のアルゴリズムは、公正なアクセスを管理するために 'turn'フラグを使用するのでより進んでいますが、seq/cstとacq/relの違いを示すために、これは必要ありません。
以下は、シーケンシャルコンシステンシーよりも弱いものが使用されている場合にアルゴリズムが壊れていることを実際に示すPetersonアルゴリズムの簡略化されたコンパイル版です。 興味深いことに、このプラットフォームではストアロードの並べ替えができるため、X86でもこの問題は解決されています。
ストアロードリオーダリングの問題点は、両方のスレッドがme
フラグをtrue
に設定して、両方のスレッドがhim
フラグからfalse
を読み取っている間に(両方のスレッドに値が伝播していないため) )、保護区域に入る。これは逐次整合性では不可能です。 gcc
で
、私は必要ではなかったclang
と、assert
火を持っている-O3
の最適化を使用してコンパイルする必要がありました。 両方のコンパイラは、異なるアプローチを使用して順次整合性を実装しています。
それはたとえば明確になり、中にバグを保つので、あなたがfetch_addとfetch_subにmemory_order_seq_cst使用する必要があります
#include <thread>
#include <atomic>
#include <assert.h>
std::atomic<bool> flag1{false};
std::atomic<bool> flag2{false};
std::atomic<int> counter{0};
// Change these to memory_order_seq_cst to fix the algorithm
static const auto store_ordering = std::memory_order_release;
static const auto load_ordering = std::memory_order_acquire;
void busy(int n)
{
auto &me = (n==1) ? flag1 : flag2;
auto &him = (n==1) ? flag2 : flag1;
for (;;)
{
for (;;)
{
me.store(true, store_ordering);
if (him.load(load_ordering) == false)
{
// got the 'lock'
break;
}
// retention, no wait period -> busy loop
me.store(false, store_ordering);
}
int tmp = counter.fetch_add(1, std::memory_order_relaxed);
assert(tmp == 0);
/*
* critical area
*/
tmp = counter.fetch_sub(1, std::memory_order_relaxed);
assert(tmp == 1);
me.store(false, store_ordering);
}
}
int main()
{
std::thread t1{busy, 1};
std::thread t2{busy, 2};
t1.join();
t2.join();
}
。 – 2501
2501 @ 'counter'はアルゴリズムの一部ではなく、1つだけということを証明するために、状態変数として使用あなたは一度に入ることができます。したがって、どのデータとも関係がないため、2つのRMW操作で 'relaxed'を使用しました。あなたはseq/cstがまだ例を有効にしているのは間違いありません。 – LWimsey