2017-01-01 8 views
2

マルチスレッド環境でのデータ競合を防ぐためにC++を理解していますが、mutexをクラスに追加することができます。読み取り専用のクラスにスレッドセーフな設計が必要ですか?

しかし、get()メソッドしかない以下のような単純なクラスがある場合でも、スレッドセーフの問題を考慮する必要がありますか?

class SimpleClass { 
public: 
    SimpleClass(int val) : v(val) {}; 
    int get() { return v; } 
private: 
    int v; 
}; 
+0

'SimpleClass'を構築していない限り、クラスの他のメソッドは副作用として' v'を変更することはできません。 – CoryKramer

+2

データレースには*変更*アクセスが必要です。そのようなアクセス権がない場合は、レースの可能性はありません。 –

+0

@KerrekSBあなたの説明は私の混乱を解決します。どうも! –

答えて

4

あなたのコードが危険であり、あなたが潜在的な競合状態を持っている。

class SimpleClass { 
public: 
    SimpleClass(int val) : v(val) {}; 
    int get() { return v; } 
private: 
    int v; 
}; 

void thread_1(SimpleClass& sc) 
{ 
    std::cout << sc.get() << '\n'; 
} 

void thread_2(SimpleClass& sc) 
{ 
    SimpleClass other(5); 
    sc = other; // potential race 
} 

問題は、コンパイラです代入演算子を生成しました。クラスのオブジェクトは、に割り当てられ、が割り当てられます。内部データ。

これは潜在的な競争を引き起こします。

+0

これを指摘してくれてありがとう!ですから、次のように、コピー代入コンストラクタを無効にする限り、コードは安全ですか? SimpleClass(SimpleClass const&)=削除; SimpleClass&operator =(SimpleClass const&)=削除; –

+1

@ROBOTAIはい、*コピー代入演算子*ではなくコピー代入演算子*を削除するだけですが、他のスレッドは構築中にそれにアクセスできないためです。 – Galik

3

これは確かに全体のクラスで、インスタンスが作成された後vの値を変更する方法はありません場合は、クラスが不変である、あなたは、他の保護対策は必要ありません。どんなスレッドがgetを呼び出しても、どの瞬間でも、インスタンスが初期化されたのと同じ値を取得します。競争状態の可能性はありません。

+0

割り当てはどうですか? 'SimpleClass a(1)、b(2); a = b; ' – Galik

2

コードが安全でないためには、4つの条件を満たす必要があります。 3番目のコードは、コードに書き込みまたは更新が含まれている場合にのみ発生します。

またThis so answerを参照してください。

  1. 複数のスレッドからアクセス可能なメモリ位置が存在しなければなりません。 。
  2. これらの共有メモリの場所に関連付けられているプロパティ(不変量と呼ばれることもあります)がコード内に存在し、プログラムが正しく機能するためには真でなければなりません。
  3. 第3に、この不変プロパティは、コードの一部(書込みまたは実際の更新)中に保持されません(間違っているか間違っています)。 (一時的に無効であるか、または処理の一部で間違っています)。
  4. レースが発生する(そしてコードが "スレッドセーフ"にならない)ために必要な第4の最終条件は、不変量が壊れている間に別のスレッドが共有メモリにアクセスできなければならないということです矛盾した動作や不正な動作を引き起こします。このコードは途中で第二のスレッドによって中断された場合、Bが割り当てられた後(、

    create new SimpleClass(1) in variable a 
    create new SimpleClass(2) in variable b 
    Switch a and b 
        { 
        create SimpleClass(a) into variable temp <-- with value 1 
        a=b     <-- puts reference to b into variable a 
        b=temp    <-- puts temp(value = 1) into variable b 
        } 
    

    が、一時前であった:あなたのケースで

は、以下の[擬似]コードを検討しますb)に割り当てられていると、それは悪いことになります。

EDIT。 (@元によって下された点を明確にするために)。 あなたのケースでは(このSimpleClassのケース、はい、クラスは不変であるため、それ自体のコードはクラス自体と競合することができないという点で「スレッドセーフ」です。クラスの競合状態を誘発するような方法で外部のマルチスレッドコードで使用することができませんでした。

+0

実際に競合状態があなたの例ではありますが、それはクラス定義内ではなく、それを使用しているコード内です。 SimpleClassは依然としてスレッドセーフである可能性があります。コンストラクタは、完全に構築されるまでオブジェクトへの参照がないため、本質的にスレッドセーフです。明らかに、複数のスレッドが同時に1つのインスタンスを構築することはできません。 2つのスレッドが同じクラスの新しいインスタンスを同時に作成している場合、実際には2つの独立したインスタンスを構築しています。私の答えに説明されているように、唯一の問題はメモリモデル関連の問題です。 – Juan

+0

@Juan、はい、おそらく重要なポイントは、クラスや構造体を参照する際に「スレッドセーフ」という用語を使用するのが誤解を招くことでしょう。これはコードスニペット、つまりコードのブロック(クラスの内部にあるか、安全でないコードを含まないクラスを含むクラスを使用することができます)であり、潜在的に危険である可能性があります。 –

0

不変クラスは、本質的にスレッドセーフです。

ほとんどの場合、同じデータの他の読み取りまたは書き込みと並行して書き込みを実行できるときに並行性の問題が発生します。

共有ロックと排他ロックを考慮してください。読み取りは共有ロックを取得して実行され、書き込みには排他ロックを取得する必要があります。任意の数のスレッドが同時に共有ロックを所有することができます。 1つのスレッドのみが排他ロックを同時に所有でき、排他ロックが保持されている間は共有ロックを保持できません。これは、書き込みを同時に実行することはできますが、書き込みも読み取りも書き込みも実行できないことを意味します。データを変更することができない場合は、並行性の問題は発生しません(排他ロックの必要がないため、共有ロックは意味をなさない)。

これは関数型言語の利点の1つです。データは決して変更されないため、本質的にスレッドセーフであり、積極的なコンパイラの最適化が可能です。

通常、スレッドセーフティについてはもう忘れられている質問があります。メモリモデル、特に現代のNUMAアーキテクチャです。

volatile変数について知っていれば、コンパイラは、シングルスレッド処理でprogranが正しいままである限り、データアクセスを自由に最適化できます。

他のスレッドが同時に変数を読み書きする可能性があることをコンパイラが認識していない場合、レジスタに値を保持して、メインメモリの変更をチェックしないことがあります。これは、異なるレベルのキャッシュにキャッシュされた値でも起こります。コンパイル時に条件の結果を知っていて、関係する値が非決定的に変化する可能性があることを知らない場合は、条件分岐を最適化することさえできます。

変数をvolatileと宣言すると、その値が変更され、毎回メインメモリにフラッシュされ、メインメニューからも読み込まれることを示します。

しかし、値が変更されない場合は、これがなぜ必要なのでしょうか?まあ、値は建設中に変化しますが、それは瞬間的または原子的であると仮定することはできません。コンパイラがマルチスレッドであることを知らない場合、メイン・メモリにデータをフラッシュすることさえありません。このオブジェクトへの参照を別のスレッドが利用できるようにすると、そのオブジェクトは初期化されていないメインメモリから読み込まれます。あるいは、初期化が行われているのを見ることもできます(これは、古いバージョンのjavaで大きなStringを初期化するときに起こる可能性があります)。

現代のC++標準ではメモリモデルが定義されていると思いますが、私はそれをまだ掘り下げていません。メモリモデルが指定されていない場合や十分に強くない場合は、ロックを取得または解放するなどのプリミティブを実行する必要があります。いずれにせよ、データが揮発性または不変であることをコンパイラに伝える必要があるため、使用中のメモリモデルの保証を提供することができます。

この場合、変数とgetterメソッドをconst修飾子で宣言します。私はそれが正常に動作することは確信していますが、私はあなたが使用している標準のメモリモデルを勉強し、必要に応じてより現代的な標準に切り替えることをお勧めします。

関連する問題