あなたのコードは完全に機能していません!
その理由は、変数へのアクセスがblocked
アトミックではないためです。true
最初のスレッドが更新を書き出し、更新がすべてのCPUに伝播する前に、2つの読み取りが発生した場合、2つのスレッドがそれを同時に読み取り、ミューテックスのロックが解除されていると判断できます。
これを解決するには、アトミック変数とアトミック交換が必要です。atomic_flag
タイプはまさにあなたが望むものです:
#include <atomic>
std::atomic_flag blocked;
while (blocked.test_and_set()) { } // spin while "true"
// critical work goes here
blocked.clear(); // unlock
std::atomic<bool>
(または、とを使用することもできますexchange(true)
が、atomic_flag
はこの目的のために特別に作成されています。)
アトミック変数は、これがシングルスレッドコンテキストである場合に無関係と思われるコードをコンパイラーが並べ替えるのを防ぐだけでなく、CPU自体が一貫性のない方法で命令を並べ替えるのを防ぐために必要なコードをコンパイラーに生成させます実行フロー。
実際、少し効率を上げたい場合は、次のように、セットおよびクリア操作でより安価なメモリオーダリングを要求できます。
while (blocked.test_and_set(std::memory_order_acquire)) { } // lock
// ...
blocked.clear(std::memory_order_release); // unlock
その理由は、一方向の正しい順序のみを気にするためです。他の方向の更新の遅延はそれほど費用がかかりませんが、(デフォルトのように)逐次一貫性を要求することはかなり費用がかかる可能性があります。
重要:上記のコードはいわゆるスピンロックです。これは、状態がロックされている間、ビジースピン(while
ループ)を実行するためです。これはほとんどすべての状況で非常に悪いです。カーネルが提供するmutexシステムコールは、スレッドがスリープ状態になり、カーネルがスレッド全体のスケジュールを解除できることをカーネルに通知できるため、まったく異なる魚のやかんです。ほとんどの場合、これがより良い動作です。