const
はC++ 11のthread-safeを意味すると聞きます。本当?
これは、const
がJavaのsynchronized
と同等になったことを意味しますか?
keywordsが不足していますか?
const
はC++ 11のスレッドセーフを意味すると聞きます。本当?
それはsomewhattrue ...
これは、スレッドセーフについて標準言語が言わなければならないことです。
[1.10/4]2つの式の評価conflictいずれかがメモリ位置を変更する場合(1.7)もう1つは、同じメモリ位置にアクセスまたは変更します。
[1.10/21]2つの競合するアクションが含まれている場合、プログラムの実行にはdata raceが含まれます少なくとも1つのスレッドはアトミックではなく、他のスレッドよりも前に発生することはありません。そのようなデータの競合は、未定義の動作をもたらします。
data raceが発生するのに十分な条件に過ぎません:
標準ライブラリはそれに基づいて構築されており、少し先に進んでいます:
[17.6.5.9/1]このセクションでは、データ競合を防ぐために実装が満たすべき要件を指定します(1.10)。特に指定のない限り、すべての標準ライブラリ関数は各要件を満たします。実装は、以下に指定された場合以外のデータの競合を防ぐことができます。
[17.6.5.9/3]C++標準ライブラリ関数は、他のスレッドからアクセス可能なオブジェクト(1.10)を直接または間接的に変更してはならないオブジェクトが関数の非const引数(
this
を含む)を介して直接または間接的にアクセスされない限り、現在のスレッドよりも.
簡単な言葉で言うと、const
オブジェクトに対する操作はthread-safeであると想定されています。つまり、独自のタイプのconst
オブジェクトに対する操作でも、Standard Libraryはデータ競合を引き起こしません。
この期待が型の1つに当てはまらない場合、標準ライブラリのコンポーネントと一緒に直接または間接的に使用すると、データ競合になる可能性があります。結論として、const
は標準ライブラリの観点からスレッドセーフを意味します。これは単にcontractであり、コンパイラーによって強制されないことに注意することが重要です。これを破るとndefined behaviorが得られ、自分で実行します。 const
が存在するかどうかは、少なくともdata races--に関してはコード生成に影響しません。
つまり、
const
はJavaのsynchronized
と同等になりましたか?
いいえ。どういたしまして...
長方形を表す次の非常に単純化されたクラスを検討してください。
class rect {
int width = 0, height = 0;
public:
/*...*/
void set_size( int new_width, int new_height ) {
width = new_width;
height = new_height;
}
int area() const {
return width * height;
}
};
メンバー関数area
はスレッドセーフ;です。 const
が原因ではなく、読み取り操作のみで構成されているためです。関係する書き込みはなく、data raceが発生するために少なくとも1つの書き込みが必要です。つまり、必要な数のスレッドからarea
を呼び出すことができ、常に正しい結果が得られます。
これは、rect
がthread-safeであることを意味しないことに注意してください。実際、特定のarea
に対するset_size
の呼び出しと同時にrect
の呼び出しが発生した場合、area
が古い幅と新しい高さ(またはさらには値が文字化けします)。
しかし、それは大丈夫です、rect
はconst
ではないので、結局thread-safeになることさえ期待されていません。一方、const rect
として宣言されたオブジェクトは、書き込みができないためスレッドセーフになります(元々const
で宣言されたものをconst_cast
- ingすることを検討している場合、ndefined-behaviorを取得します_ 以上です)。
それでは、それはどういう意味ですか?
-議論のために-乗算演算は非常にコストがかかると仮定しましょう。可能な限りそれらを避けるべきです。要求された場合にのみ領域を計算し、将来再び要求された場合に備えてキャッシュすることができます。
class rect {
int width = 0, height = 0;
mutable int cached_area = 0;
mutable bool cached_area_valid = true;
public:
/*...*/
void set_size( int new_width, int new_height ) {
cached_area_valid = ( width == new_width && height == new_height );
width = new_width;
height = new_height;
}
int area() const {
if( !cached_area_valid ) {
cached_area = width;
cached_area *= height;
cached_area_valid = true;
}
return cached_area;
}
};
[この例が人工的すぎると思われる場合は、int
を、本質的にスレッドセーフでなく乗算が非常に高価な非常に大きな動的に割り当てられた整数に精神的に置き換えることができます。
メンバー関数area
はスレッドセーフではなくなりました。書き込みを行っており、内部的に同期されていません。それって問題ですか? area
への呼び出しは、別のオブジェクトのcopy-constructorの一部として発生する場合があります。たとえば、constructorは標準コンテナの操作によって呼び出される可能性があります。その時点で、標準ライブラリは、この操作がデータ競合に関してreadとして動作することを期待しています。しかし、私たちは書き込みを行っています!
rect
を標準コンテナに直接または間接的に配置すると、すぐに標準ライブラリとともに契約を入力します。 const
関数で書き込みを続けながら、その契約を尊重するには、これらの書き込みを内部で同期する必要があります。
class rect {
int width = 0, height = 0;
mutable std::mutex cache_mutex;
mutable int cached_area = 0;
mutable bool cached_area_valid = true;
public:
/*...*/
void set_size( int new_width, int new_height ) {
if( new_width != width || new_height != height )
{
std::lock_guard< std::mutex > guard( cache_mutex );
cached_area_valid = false;
}
width = new_width;
height = new_height;
}
int area() const {
std::lock_guard< std::mutex > guard( cache_mutex );
if( !cached_area_valid ) {
cached_area = width;
cached_area *= height;
cached_area_valid = true;
}
return cached_area;
}
};
area
関数thread-safeを作成しましたが、rect
はまだthread-safeではありません。 area
とwidth
への割り当てがミューテックスによって保護されていないため、set_size
への呼び出しが間違った値を計算することになり、同時にheight
への呼び出しが発生する可能性があります。
本当にスレッドセーフrect
が必要な場合は、同期プリミティブを使用してスレッドセーフではないrect
を保護します。
keywordが不足していますか?
はい、そうです。初日からキーワードが不足しています。
ソース: const
およびmutable
がわからない -Herb Sutter