1. ホーム
  2. c++

[解決済み] C++11でconstはスレッドセーフを意味するのか?

2022-08-09 14:38:39

質問

私は、次のように聞いています。 const というのは スレッドセーフ C++11 . それは本当ですか?

ということは const は、現在では Java 's synchronized ?

を使い果たしたのでしょうか? キーワード ?

どのように解決するのですか?

<ブロッククオート

私はそれを聞いて const というのは スレッドセーフ C++11 . それは本当ですか?

それは 多少 の通りです.

これは 標準言語 はスレッドセーフについて述べています。

<ブロッククオート

[1.10/4] つの式の評価 衝突 もし一方がメモリ位置 (1.7) を変更し、もう一方が同じメモリ位置にアクセスするか変更するならば。

[1.10/21] プログラムの実行には データレース を含む場合、そのプログラムの実行は、異なるスレッドで2つの競合するアクションを含み、そのうちの少なくとも1つはアトミックではなく、どちらも他より前に発生しません。そのようなデータ競合は、未定義の動作になります。

の十分条件にほかなりません。 データ・レース が発生する十分条件に他なりません。

  1. ある物に対して同時に2つ以上のアクションが実行されていること、および
  2. そのうちの少なくとも 1 つは書き込みである。

標準ライブラリ はそれをベースにして、もう少し先に進みます。

[17.6.5.9/1] この節では,データ競合 (1.10) を防ぐために,実装が満たさなければならない要件を規定する。すべての標準ライブラリ関数は、特に指定がない限り、各要件を満たさなければならない。実装は、以下に指定された以外のケースでデータレースを防止することができます。

[17.6.5.9/3] C++標準ライブラリ関数は、現在のスレッド以外のスレッドからアクセス可能なオブジェクト (1.10) を直接または間接的に変更してはなりません。ただし、オブジェクトが関数の非 const を含む引数 this .

に対する操作を期待していることを端的に表しています。 const オブジェクトへの操作は スレッドセーフ . これは 標準ライブラリ に対する操作がある限り、データ競合は起こらないということです。 const オブジェクトを操作する限り、データ競合は発生しません。

  1. 読み込みのみで構成される -- つまり、書き込みはない -- あるいは
  2. 書き込みを内部的に同期させる。

もしこの期待があなたの型の一つで成り立たないのであれば、それを直接または間接的に 標準ライブラリ が発生する可能性があります。 データ競合 . 結論から言うと const スレッドセーフ を意味します。 標準ライブラリ の観点で説明します。重要なのは、これは単に 契約 であり、コンパイラによって強制されることはなく、もしこれを破った場合は 未定義の動作 となり、自己責任となります。もし const があってもなくても、コード生成には影響しません。 データレース --.

ということでしょうか。 const と同等になったということでしょうか。 Java 's 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 でなく、読み込み操作だけで構成されているからです。書き込みはなく、少なくとも1回の書き込みが必要なのは データ競争 が発生します。つまり area を好きなだけ呼び出すことができ、常に正しい結果が得られるということです。

という意味ではないことに注意してください。 rect スレッドセーフ . 実際,もし area を呼び出すと同時に発生したものです。 set_size への呼び出しが同時に起こったとします。 rect であれば area は古い幅と新しい高さに基づいて(あるいは文字化けした値に基づいて)その結果を計算することになるかもしれません。

しかし、それは大丈夫です。 rectconst であることは期待されていません。 スレッドセーフ ということになります。宣言されたオブジェクトは const rect と宣言されたオブジェクトは、一方では スレッドセーフ というのは、書き込みができないからです(そして、もしあなたが const_cast -と宣言されたものを const であれば、次のようになります。 未定義の振る舞い と表示され、それでおしまいです)。

では、どういうことなのでしょうか?

議論のために、乗算演算は非常にコストがかかるので、可能な限り避けた方がよいと仮定しましょう。面積が要求された場合のみ計算し、将来再び要求された場合に備えてキャッシュしておくことができます。

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 の一部として起こるかもしれません。 コピーコンストラクタ のような、他のオブジェクトの コンストラクタ に対する何らかの操作によって呼び出された可能性があります。 標準コンテナ に対する何らかの操作によって呼び出された可能性があり、その時点では 標準ライブラリ として動作することを期待します。 読む に関して データ・レース . しかし、私たちは書き込みをしているのです!

を置くと同時に 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 機能 をスレッドセーフにしました。 しかし rect はまだ スレッドセーフ . への呼び出しは area への呼び出しと同時に起こる set_size への代入はまだ間違った値を計算することになるかもしれません。 widthheight はミューテックスで保護されません。

もし本当に スレッドセーフ rect を保護するために、同期プリミティブを使用することになります。 スレッドセーフでない rect .

を使い切っているのでしょうか? キーワード ?

はい、そうです。彼らは キーワード を使い果たしているのです。


ソース : あなたは知らない constmutable - ハーブ・サッター