c++ 11 のアトミック変数の lock_free プロパティが何を意味するのかを理解したかったのです。私はグーグルで検索し、このフォーラムで他の関連する質問を見ましたが、まだ部分的に理解しています. 誰かがそれをエンドツーエンドで簡単な方法で説明できるかどうかに感謝します.
3 に答える
ロックフリーでなかった場合に何が起こるかについて話すことから始めるのがおそらく最も簡単でしょう。
ほとんどのアトミック タスクを処理する最も明白な方法は、ロックすることです。たとえば、一度に 1 つのスレッドだけが変数に書き込むようにするには、ミューテックスで変数を保護できます。変数に書き込むコードは、書き込みを行う前にミューテックスを取得する必要があります (そして後で解放します)。一度に 1 つのスレッドのみがミューテックスを所有できるため、すべてのスレッドがプロトコルに従っている限り、任意の時点で複数のスレッドを書き込むことはできません。
ただし、注意しないと、デッドロックが発生する可能性があります。たとえば、アトミック操作として2 つの異なる変数 (それぞれがミューテックスによって保護されている) に書き込む必要があるとします。つまり、一方に書き込むときは、もう一方にも書き込む必要があります)。このような場合、注意しないとデッドロックが発生する可能性があります。たとえば、2 つのミューテックス A と B を呼び出します。スレッド 1 はミューテックス A を取得し、次にミューテックス B を取得しようとします。同時に、スレッド 2 はミューテックス B を取得し、次にミューテックス A を取得しようとします。それぞれが 1 つのミューテックスを保持しているため、 、どちらも両方を取得することはできず、どちらもその目標に向かって進むことはできません。
それらを回避するためのさまざまな戦略があります (たとえば、すべてのスレッドが常に同じ順序でミューテックスを取得しようとする、または妥当な時間内にミューテックスを取得できなかった場合、各スレッドは保持しているミューテックスを解放し、ランダムな量のミューテックスを待機します)。再試行します)。
ただし、ロックフリー プログラミングでは、(明らかに十分に) ロックを使用しません。これは、上記のようなデッドロックが発生しないことを意味します。適切に実行すると、すべてのスレッドが目標に向かって継続的に進行することが保証されます。一般に信じられていることとは反対に、コードがロックを使用して適切に記述されたコードよりも速く実行されるとは限りません。ただし、上記のようなデッドロック (およびライブロックやある種の競合状態などの他のタイプの問題) が排除されることを意味します。
さて、正確にどのようにそれを行うかについて: 答えは短くて簡単です: それはさまざまです - 広く。多くの場合、特定の特定の操作をアトミックに実行するための特定のハードウェア サポートを見ています。コードはそれらを直接使用するか、それらを拡張して、アトミックでロックフリーのより高いレベルの操作を提供します。ハードウェアのサポートなしでロックフリーのアトミック操作を実装することも可能です (ただし、実用的であることはめったにありません) (ただし、その非実用性を考えると、少なくとも今のところは、これについて詳しく説明することは控えます)。
Jerry はすでに、ロックに関する一般的な正確性の問題について言及しました。つまり、ロックは理解しにくく、正しくプログラムするのが難しいということです。
ロックに関するもう 1 つの危険は、実行時間に関する決定性を失うことです。ロックを取得したスレッドが遅延した場合 (たとえば、オペレーティング システムによってスケジュールが解除されたり、「スワップ アウト」されたり)、プログラム全体が遅延する可能性があります。ロックを待っています。対照的に、ロックフリー アルゴリズムは、いくらかのスレッドが別の場所で停止していても、常にある程度の進行が保証されます。
概して、ロックフリー プログラミングは、非アトミック操作を使用するロック プログラミングよりも遅くなることがよくあります(場合によっては大幅に遅くなります)。ただし、決定論とレイテンシーの上限を提供します (少なくともプロセスの全体的なレイテンシー; @J99 が観察したように、十分な数の他のスレッドが進行している限り、個々のスレッドは依然として不足している可能性があります)。あなたのプログラムはかなり遅くなるかもしれませんが、完全にロックアップすることはなく、常に何らかの進歩を遂げます。
ハードウェア アーキテクチャの性質上、特定の小さな操作を本質的にアトミックにすることができます。実際、これはマルチタスクとマルチスレッドをサポートするすべてのハードウェアで非常に必要です。ミューテックスなどの同期プリミティブの中心には、正しいロック動作を保証するある種のアトミックな命令が必要です。
したがって、これを念頭に置いて、ブール値やマシンサイズの整数などの特定の型をアトミックにロード、保存、交換できることがわかりました。したがって、そのような型をstd::atomic
テンプレートにラップすると、結果のデータ型が実際にロックを使用しないロード、ストア、および交換操作を提供することが期待できます。対照的に、ライブラリの実装では常に、ロックによって保護されFoo
た通常のアトミックとして実装できます。Foo
アトミックオブジェクトがロックフリーかどうかをテストするには、is_lock_free
メンバー関数を使用できます。さらに、アトミック プリミティブ型が潜在的にロックフリーのインスタンス化を持っているATOMIC_*_LOCK_FREE
かどうかを示すマクロがあります。ロックフリーにしたい並行アルゴリズムを書いている場合は、アトミックオブジェクトが実際にロックフリーであるというアサーション、または値を持つマクロの静的アサーションを含める必要があります(対応する型のすべてのオブジェクトがは常にロックフリーです)。2