constはC ++ 11でスレッドセーフを意味しますか?


115

私はそれを聞くconst手段をスレッドセーフにしてC ++ 11。本当?

constこれは、Javaの同等の機能になるということsynchronizedですか。

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


1
C ++-faqは通常C ++コミュニティによって管理されており、チャットでご意見をお寄せください。
子犬

@DeadMG:C ++のよくある質問とそのエチケットは知らなかったので、コメントで提案されました。
K-ballo 2013年

2
constがスレッドセーフであることをどこで聞いたのですか?
マークB

2
@Mark B:Herb SutterBjarne StroustrupStandard C ++ Foundationでそう言っていました。回答の下部にあるリンクを参照してください。
K-ballo 2013年

ここに来るそれらへの注意:本当の質問はスレッドセーフをconst 意味するかどうかではありません。それ以外の場合は、先に進んですべてのスレッドセーフメソッドをとしてマークできるはずなので、それはナンセンスconstです。むしろ、私たちが実際に求めている質問は、スレッドセーフであるconst IMPLIESです。それが、この議論に関するものです。
user541686

回答:


131

私はそれを聞くconst手段をスレッドセーフにしてC ++ 11。本当?

それはやや本当です...

これは、標準言語がスレッドセーフに関して言っていることです。

[1.10 / 4] 2つの式の評価は、一方がメモリロケーション(1.7)を変更し、もう一方が同じメモリロケーションにアクセスまたは変更すると競合します。

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

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

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

標準ライブラリは、さらに少し行くと、その上に構築します:

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

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

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

  1. 完全に読み取りで構成されている(つまり、書き込みがない)。または
  2. 内部で書き込みを同期します。

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

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

これrectスレッドセーフであるという意味ではないことに注意してください。実際には、その簡単にはへの呼び出しがあればどのように確認しareaたの呼び出しと同時に起こるようにset_size与えられた上でrect、その後、area(文字化け値に偶数か)古い幅と新しい高さに基づいてその結果を計算してしまう可能性があり。

しかし、それは問題rectありません。結局のところ、スレッドセーフであるconstとは予想されていません。一方、宣言されたオブジェクトは、書き込みが不可能であるためスレッドセーフになります(そして、最初に宣言されたものを検討すると、未定義の動作が発生します)。const 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によって、非常に大きな動的に割り当てられた整数本質的に非でスレッドセーフと乗算は非常に高価であるため。]

メンバー関数は area、もはやれるスレッドセーフ、それは今の書き込みをしていないと内部で同期されていません。それって問題ですか?への呼び出しは、別のオブジェクトのコピーコンストラクターのarea一部として発生する可能性があります。このようなコンストラクターは、標準コンテナーの操作によって呼び出された可能性があり、その時点で標準ライブラリは、この操作がデータ競合に関して読み取りとして動作すると想定しています。しかし、書き込みを行っています!

すぐに私たちが置かようrect標準コンテナ --directlyまたはindirectly--我々が入っている契約標準ライブラリを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まだに割り当てているので、間違った値を計算することになるかもしれないwidthし、heightミューテックスで保護されていません。

スレッドセーフが 本当に必要な場合はrect、同期プリミティブを使用して非スレッドセーフ を保護しますrect

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

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


出典あなたは知らないconstmutable - ハーブサッター


6
@Ben Voigt:のC ++ 11仕様std::stringがすでにCOWを禁止する方法で記述されていることは私の理解です。しかし、具体的なことは覚えていません...
K-ballo 2013年

3
@BenVoigt:いいえ。それは、そのようなものが非同期化されるのを防ぐだけです。つまり、スレッドセーフではありません。C ++ 11はすでに明示的にCOWを禁止しています。ただし、この特定の節はそれとは関係がなく、COWを禁止しません。
子犬

2
論理的なギャップがあるように私には思えます。[17.6.5.9/3]「直接的または間接的に変更してはならない」と述べて「多すぎる」ことを禁止する; 「直接的または間接的にデータ競合を導入してはならない」アトミックな書き込みが「変更」ではないと定義されている場合を除き、。しかし、私はこれをどこにも見つけることができません。
Andy Prowl 2013年

1
私はおそらく私の全体的なポイントをここで少し明確にしました: したと思います isocpp.org/blog/2012/12/…とにかく助けてくれてありがとうございます。
Andy Prowl 2013年

1
時々、これらのようないくつかの標準的な段落を書き留めるのに実際に責任を負ったのは誰なのか(または直接関与したのか)疑問に思います。
pepper_chico 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.