mutexの存在はvolatileキーワードを取り除くのに役立ちますか?
-
06-07-2019 - |
質問
読み取り、書き込み、保留中の読み取り、保留中の書き込みカウンターを保持するマルチR / Wロッククラスがあります。ミューテックスは、複数のスレッドからそれらを保護します。
質問は、コンパイラーが最適化の実行中にそれを台無しにしないように、まだカウンターを揮発性として宣言する必要があるかどうかです。
または、コンパイラーは、カウンターがミューテックスによって保護されていることを考慮しますか?
相互排他ロックは、同期と「揮発性」のためのランタイムメカニズムであることを理解しています。 keywordは、最適化を行いながら正しいことを行うためのコンパイラーへのコンパイル時の指示です。
よろしく、 -ジェイ。
解決
ここには2つの基本的に無関係なアイテムがあり、常に混同されています。
- 揮発性
- スレッド、ロック、メモリバリアなど
volatileは、コンパイラに、レジスタからではなくメモリから変数を読み取るコードを生成するように指示するために使用されます。また、コードを並べ替えないようにします。一般的に、最適化や「ショートカット」を行わないようにします。
別の回答でハーブサッターから引用されているように、メモリバリア(ミューテックス、ロックなどによって提供されます)は、コンパイラがどのように言ったかに関係なく、 CPU が読み取り/書き込みメモリリクエストを並べ替えないようにするためのものですそれを行うには。つまり、CPUレベルで、最適化せず、ショートカットを取らないでください。
似ていますが、実際には非常に異なっています。
あなたの場合、そしてほとんどのロックの場合、volatileが必要ない理由は、ロックのために関数呼び出しが行われているためです。例:
最適化に影響する通常の関数呼び出し:
external void library_func(); // from some external library
global int x;
int f()
{
x = 2;
library_func();
return x; // x is reloaded because it may have changed
}
コンパイラがlibrary_func()を調べてxに触れていないと判断できない限り、戻り時にxを再読み取りします。これは、揮発性がなくてもです。
スレッディング:
int f(SomeObject & obj)
{
int temp1;
int temp2;
int temp3;
int temp1 = obj.x;
lock(obj.mutex); // really should use RAII
temp2 = obj.x;
temp3 = obj.x;
unlock(obj.mutex);
return temp;
}
temp1のobj.xを読み込んだ後、コンパイラはtemp2のobj.xを再読み込みします-ロックの魔法のためではなく-lock()がobjを変更したかどうか不明です。おそらくコンパイラフラグを設定して、積極的に最適化する(エイリアスなしなど)ため、xを再読み取りすることはできませんが、コードの束が失敗する可能性があります。
temp3の場合、コンパイラは(できれば)obj.xを再読み取りしません。 何らかの理由でobj.xがtemp2とtemp3の間で変更される可能性がある場合は、volatileを使用します(そしてロックが壊れている/役に立たないでしょう)。
最後に、lock()/ unlock()関数が何らかの形でインライン化された場合、コンパイラーはコードを評価して、obj.xが変更されないことを確認できます。ただし、ここでは次の2つのことのいずれかを保証します。 -インラインコードは、最終的にOSレベルのロック関数を呼び出します(したがって、評価を妨げます)。 -コンパイラが認識し、並べ替えを回避するasmメモリバリア命令(つまり、__ InterlockedCompareExchangeのようなインライン関数でラップされている)を呼び出します。
編集:P.S.言及するのを忘れました-pthreadの場合、一部のコンパイラは「POSIX準拠」とマークされています。これは、とりわけ、pthread_関数を認識し、それらの周りで悪い最適化を行わないことを意味します。つまり、C ++標準ではまだスレッドについて言及されていませんが、それらのコンパイラーは(少なくとも最小限)言及しています。
だから、短い答え
volatileは必要ありません。
他のヒント
ハーブサッターの記事「クリティカルセクション(ロックが望ましい)を使用して人種を排除する」から( http://www.ddj.com/cpp/201804238 ):
したがって、並べ替え変換を有効にするには、クリティカルセクションの1つの重要なルールに従うことにより、プログラムのクリティカルセクションを尊重する必要があります。コードはクリティカルセクションから移動できません。 (コードが入っても大丈夫です。)図1の矢印で示すように、クリティカルセクションの開始と終了に対称的な一方向のフェンスセマンティクスを要求することにより、このゴールデンルールを実施します。
- クリティカルセクションに入ることは、取得操作または暗黙的な取得フェンスです。コードは、フェンスを上方向に横切ることはできません。つまり、フェンスの後に元の場所から移動して、フェンスの前に実行します。ただし、ソースコードの順序でフェンスの前に表示されるコードは、後で実行するためにフェンスを下向きにうまく通過できます。
- クリティカルセクションを終了することは、リリース操作、または暗黙のリリースフェンスです。これは、コードがフェンスを下方向に通過できず、上方向にのみ通過できるという逆の要件です。最終リリースの書き込みを参照する他のスレッドも、その前のすべての書き込みを参照することを保証します。
つまり、コンパイラがターゲットプラットフォームの正しいコードを生成するために、クリティカルセクションが出入りするとき(およびクリティカルセクションという用語は、<コードによって保護された何かのWin32の意味ではなく、一般的な意味で使用されます) > CRITICAL_SECTION 構造-クリティカルセクションは他の同期オブジェクトによって保護できます)正しい取得およびリリースセマンティクスに従う必要があります。したがって、保護されたクリティカルセクション内でのみアクセスされる限り、シェア変数を揮発性としてマークする必要はありません。
volatileは、レジスタにロードして変更しないと仮定するのではなく、ロケーションの現在の値を常にロードするようオプティマイザーに通知するために使用されます。これは、デュアルポートメモリの場所またはスレッド外部のソースからリアルタイムで更新できる場所を操作する場合に最も役立ちます。
ミューテックスは、コンパイラが実際には何も知らないランタイムOSメカニズムです。したがって、オプティマイザはそれを考慮しません。一度に複数のスレッドがカウンターにアクセスするのを防ぎますが、それらのカウンターの値は、ミューテックスが有効な間でも変更される可能性があります。
つまり、varsを揮発性としてマークしているのは、それらが外部から変更できるからであり、mutexガード内にあるからではありません。
揮発性を保ちます。
まだ「揮発性」が必要です;キーワード。
ミューテックスは、カウンターによる同時アクセスを防ぎます。
&quot; volatile&quot;実際にカウンターを使用するようコンパイラーに指示します CPUレジスタにキャッシュする代わりに(これは 並行スレッドによって更新されます。)