次のようにSpinLockクラスを実装しました
struct Node {
int number;
std::atomic_bool latch;
void add() {
lock();
number++;
unlock();
}
void lock() {
bool unlatched = false;
while(!latch.compare_exchange_weak(unlatched, true, std::memory_order_acquire));
}
void unlock() {
latch.store(false , std::memory_order_release);
}
};
上記のクラスを実装し、Node classの同じインスタンスのadd()メソッドをスレッドごとに1,000万回呼び出す2つのスレッドを作成しました。
結果は、残念ながら2000万ではありません。ここで何が欠けていますか?
問題は、compare_exchange_weak
が失敗するとunlatched
変数を更新することです。 compare_exchange_weak
のドキュメントから:
アトミックオブジェクトに含まれる値の内容を期待値と比較します。-trueの場合、含まれている値をval(storeなど)に置き換えます。 -falseの場合、期待値を含まれる値に置き換えます。
すなわち、最初に失敗したcompare_exchange_weak
の後、unlatched
はtrue
に更新されるため、次のループの繰り返しはcompare_exchange_weak
true
でtrue
。これは成功し、別のスレッドが保持しているロックを取得しました。
解決策:各compare_exchange_weak
の前に、必ずunlatched
をfalse
に戻すようにしてください。例:
while(!latch.compare_exchange_weak(unlatched, true, std::memory_order_acquire)) {
unlatched = false;
}
@gexicideで述べたように、問題は_compare_exchange
_関数がアトミック変数の現在の値でexpected
変数を更新することです。それが理由であり、そもそもローカル変数unlatched
を使用しなければならない理由です。これを解決するために、各ループの繰り返しでunlatched
をfalseに戻すことができます。
ただし、インターフェイスがあまり適していないものに_compare_exchange
_を使用する代わりに、代わりに_std::atomic_flag
_を使用する方がはるかに簡単です。
_class SpinLock {
std::atomic_flag locked = ATOMIC_FLAG_INIT ;
public:
void lock() {
while (locked.test_and_set(std::memory_order_acquire)) { ; }
}
void unlock() {
locked.clear(std::memory_order_release);
}
};
_
ソース: cppreference
手動でメモリの順序を指定することは、ソースからコピーした潜在的なパフォーマンスの微調整にすぎません。パフォーマンスの最後の部分よりも単純さが重要な場合は、デフォルト値のままにしてlocked.test_and_set() / locked.clear()
を呼び出すことができます。
ところで:_std::atomic_flag
_は、ロックフリーであることが保証されている唯一のタイプですが、_std::atomic_bool
_の操作がロックフリーではないプラットフォームは知りません。
更新:@David Schwartz、@ Anton、および@Technik Empireのコメントで説明されているように、空のループには、分岐ミス予測、スレッド枯渇などの望ましくない効果がありますHTプロセッサと非常に高い電力消費で-つまり、待機するのはかなり非効率的な方法です。影響とソリューションは、アーキテクチャ、プラットフォーム、およびアプリケーション固有です。私は専門家ではありませんが、通常の解決策は、Linuxのcpu_relax()
またはwindowsのYieldProcessor()
をループ本体に追加することです。
EDIT2:明確にするために:ここに示されている移植可能なバージョン(特別なcpu_relaxなどの指示なし)は、多くのアプリケーションで十分であるはずです。他の誰かが長時間ロックを保持しているためにSpinLock
が頻繁に回転する場合(これは、一般的な設計上の問題を既に示している可能性があります)、とにかく通常のミューテックスを使用する方がよいでしょう。