125

これは、C++11ではスレッドセーフconstを意味すると聞きました。本当?

つまり、 Javaconst同等になりましたか?synchronized

キーワードが不足していませんか?

4

2 に答える 2

142

これは、C++11ではスレッドセーフconstを意味すると聞きました。本当?

それはやや真実です...

これは、標準語がスレッドセーフについて述べていることです。

[1.10 / 4] 2つの式の評価は、一方がメモリ位置(1.7)を変更し、もう一方が同じメモリ位置にアクセスまたは変更した場合に競合します。

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

これは、データ競合が発生するための十分な条件に他なりません。

  1. 特定のものに対して同時に実行されている2つ以上のアクションがあります。と
  2. それらの少なくとも1つは書き込みです。

標準ライブラリはその上に構築されており、もう少し進んでいます。

[17.6.5.9/1] このセクションでは、データの競合を防ぐために実装が満たす必要のある要件を指定します(1.10)。特に指定がない限り、すべての標準ライブラリ関数は各要件を満たす必要があります。実装により、以下に指定されている以外の場合にデータの競合が防止される場合があります。

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

これは簡単に言うと、constオブジェクトに対する操作がスレッドセーフであることを期待しているということです。これは、独自のタイプのオブジェクトに対する操作が行われている限り、標準ライブラリがデータ競合を導入しないことを意味します。const

  1. 完全に読み取りで構成されます(つまり、書き込みはありません)。また
  2. 書き込みを内部的に同期します。

この期待がいずれかのタイプに当てはまらない場合は、標準ライブラリのコンポーネントと一緒に直接または間接的に使用すると、データ競合が発生する可能性があります。結論として、標準ライブラリの観点からはスレッドセーフconstを意味します。これは単なる契約であり、コンパイラによって強制されることはないことに注意することが重要です。これを破ると、未定義の動作が発生し、自分自身で実行されます。存在するかどうかは、コード生成に影響しません-少なくともデータの競合に関しては-。const

つまり、 Javaconst同等になりましたか?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です; だからではなく、完全に読み取り操作で構成されているからです。関係する書き込みはなく、データ競合が発生するためには少なくとも1回の書き込みが必要です。つまり、必要な数のスレッドから呼び出すことができ、常に正しい結果が得られます。constarea

rectこれは、それがスレッドセーフであることを意味するものではないことに注意してください。実際、への呼び出しが特定のでの呼び出しと同時に発生した場合、古い幅と新しい高さ(または文字化けした値)に基づいて結果を計算する方法を簡単に確認できareaます。 。set_sizerectarea

しかし、それは大丈夫です。結局のところ、スレッドセーフであるとは期待されていませんrect。一方、宣言されたオブジェクトは、書き込みが不可能なため、スレッドセーフになります(最初に宣言されたものを検討している場合は、未定義動作になります)。constconst rectconst_castconst

では、それはどういう意味ですか?

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

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精神的に置き換えることができます。]

member-functionは スレッドセーフ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を保護します。

キーワードが不足していませんか?

はい、そうです。彼らは初日からキーワードを使い果たしています。


ソースあなたは知らないconstそしてmutable-ハーブサッター

于 2013-01-02T18:43:20.413 に答える
1

これはK-balloの答えへの追加です。

この文脈では、スレッドセーフという用語が乱用されています。正しい言い回しは次のとおりです。ハーブサッター(29:43)自身が述べているように、const関数はスレッドセーフなビット単位のconstまたは内部同期を意味します

別のスレッドで同時に非const関数を呼び出さに、複数のスレッドから同時にconst関数を呼び出すことはスレッドセーフである必要があります。

したがって、const関数は、別の非const関数によって変更される可能性のあるメモリ(内部同期なし)を読み取る可能性があるため、実際にはスレッドセーフではありません(ほとんどの場合はそうではありません)。一般に、これはスレッドセーフではありません。1つのスレッドだけが書き込みを行っている(そして別のスレッドがデータを読み取っている)場合でも、データの競合が発生するためです。

于 2021-04-21T18:17:52.800 に答える