C++ メモリ モデル

C++11 以降、C++ にはメモリ モデルがあります。マルチスレッドの基礎です。それがなければ、マルチスレッドは明確に定義されていません。

C++ メモリ モデルは 2 つの側面で構成されています。一方では、記憶モデルの非常に複雑な部分があり、これはしばしば私たちの直感に反します。一方、メモリ モデルは、マルチスレッドの課題をより深く理解するのに大いに役立ちます。

契約

最初のアプローチでは、C++ メモリ モデルがコントラクトを定義します。この契約は、プログラマとシステムの間で確立されます。システムは、プログラムをアセンブラ命令にコンパイルするコンパイラ、アセンブラ命令を実行するプロセッサ、およびプログラムの状態を格納するさまざまなキャッシュで構成されます。契約では、プログラマーが特定の規則に従うことを要求し、規則が破られない限り、プログラムを最適化するための完全な権限をシステムに与えます。その結果、(良い場合には) 明確に定義された、最大限に最適化されたプログラムが得られます。正確に言えば、単一のコントラクトだけでなく、きめの細かい一連のコントラクトがあります。あるいは違う言い方をする。プログラマーが従わなければならない規則が弱いほど、システムが高度に最適化された実行可能ファイルを生成する可能性が高くなります。

経験則は非常に簡単です。コントラクトが強いほど、システムが最適化された実行可能ファイルを生成する自由が少なくなります。残念ながら、その逆はうまくいきません。プログラマーが非常に弱いコントラクトまたはメモリ モデルを使用する場合、多くの最適化の選択肢があります。しかし、このプログラムを管理できるのは、世界的に有名な少数の専門家だけです。

C++11 には 3 つのレベルのコントラクトがあります。

C++11 より前は、コントラクトは 1 つしかありませんでした。 C++ は、マルチスレッドやアトミックの存在を認識していませんでした。システムは 1 つの制御フローしか認識していないため、実行可能ファイルを最適化する機会は限られていました。このシステムの重要なポイントは、プログラムの観察された動作がソース コード内の命令のシーケンスに対応しているという錯覚をプログラマーに維持することでした。もちろん、メモリモデルはありませんでした。その代わりに、シーケンス ポイントの概念がありました。シーケンス ポイントは、プログラム内のポイントであり、そのポイントで前のすべての命令の効果が観察可能でなければなりません。関数の実行の開始または終了は、シーケンス ポイントです。ただし、2 つの引数を指定して関数を呼び出す場合、C++ 標準では、どの引数が最初に評価されるかは保証されません。したがって、動作は指定されていません。理由は簡単です。コンマ演算子はシーケンス ポイントではありません。これは C++11 でも変わりません。

しかし、C++ ではすべてが変わります。 C++11 は、初めて複数のスレッドを認識します。スレッドの動作が明確に定義されている理由は、C++ メモリ モデルです。 C++ のメモリ モデルは Java のメモリ モデルに着想を得ていますが、C++ のメモリ モデルは、これまでどおり、さらに数歩進んでいます。しかし、それは次の投稿のトピックになります。したがって、プログラマーは、明確に定義されたプログラムを作成するために、共有変数を処理する際にいくつかの規則に従わなければなりません。少なくとも 1 つのデータ競合が存在する場合、プログラムは未定義です。既に述べたように、スレッドが変更可能なデータを共有する場合、データ競合に注意する必要があります。そのため、タスクはスレッドや条件変数よりもはるかに使いやすいです。

アトミックでは、専門家の領域に入ります。これは、C++ メモリ モデルを弱体化すればするほど、より明白になります。アトミックを使用する場合、ロックフリー プログラミングについてよく話します。弱いルールと強いルールについて投稿で話しました。実際、シーケンシャル一貫性はストロング メモリ モデル、緩和されたセマンティック ウィーク メモリ モデルと呼ばれます。

契約の内容

プログラマーとシステムの間の契約は、次の 3 つの部分で構成されます。

  • アトミック オペレーション :中断することなく実行される操作
  • オペレーションの部分的な順序 :操作の順序。変更できません。
  • 作戦の目に見える影響 :共有変数に対する操作が別のスレッドでいつ可視になるかを保証します。

コントラクトの基盤は、アトミックに関する操作です。これらの操作には 2 つの特徴があります。それらはアトミックであり、プログラムの実行時に同期と順序の制約を作成します。これらの同期と順序の制約は、多くの場合、非アトミック操作にも当てはまります。アトミック操作は常にアトミックですが、必要に応じて同期と順序の制約を調整できます。

全体像に戻る

記憶モデルを弱めれば弱めるほど、焦点は変わります。

  • システムのさらなる最適化の可能性
  • プログラムの制御フローの数は指数関数的に増加します
  • エキスパート向けドメイン
  • 直感の崩壊
  • マイクロ最適化の領域

マルチスレッドを作成するには、エキスパートになる必要があります。アトミック (順次整合性) を扱いたい場合は、次の専門知識レベルへの扉を開く必要があります。取得と解放、または緩和されたセマンティックについて話すとどうなるでしょうか。毎回、次の専門知識レベルへと 1 段階高くなります。

次は?

次の投稿では、C++ メモリ モデルについて詳しく説明します。というわけで、次の記事はロックフリープログラミングについてです。この旅では、アトミックとその操作について話します。基本が完了した場合、メモリ モデルのさまざまなレベルが続きます。開始点は単純な順次整合性であり、取得と解放のセマンティックが続き、それほど直感的ではないリラックスしたセマンティックが終了点になります。次の投稿は、アトミック操作のデフォルトの動作に関するものです:順次整合性。 (校正者 Alexey Elymanov )