7

競合状態と、複数のスレッドが同じ変数にアクセスしている場合、あるスレッドによって行われた更新が無視され、他のスレッドによって上書きされる可能性があることを理解していますが、各スレッドが同じ値 (異なる値ではない) を同じ変数に書き込んでいる場合はどうでしょうか。これでも問題を引き起こす可能性がありますか?このコードは:

GlobalVar.property = 11;

(そのプロパティが 11 以外に割り当てられることはないと仮定すると)、複数のスレッドが同時にそれを実行すると問題が発生しますか?

4

7 に答える 7

9

問題は、その状態を読み返し、それについて何かを行うときに発生します。書き込みは危険です。これが 1 つの単語である限り、ほとんどの環境では書き込みがアトミックであることを保証しますが、それは、このフラグメントを含むより大きなコードがスレッドセーフであることを意味しません。まず、おそらくグローバル変数には最初から異なる値が含まれていたと思われます。それ以外の場合、それが常に同じであることがわかっている場合、なぜ変数なのですか? 第二に、おそらく最終的にこの値をもう一度読み返すと思いますか?

問題は、おそらく、何らかの理由でこのビットの共有状態に書き込みを行っていることです。何かが発生したことを知らせるためですか? ここで問題が発生します。ロック構造がない場合、メモリ アクセスの暗黙の順序はまったくありません。あなたの例には実際には変数の使用が含まれていないため、ここで何が問題なのかを指摘するのは難しいため、ニュートラルな C ライクな構文の簡単な例を次に示します。

int x = 0, y = 0;

//thread A does:
x = 1;
y = 2;
if (y == 2)
    print(x);

//thread B does, at the same time:
if (y == 2)
    print(x);

スレッド A は常に 1 を出力しますが、スレッド B が 0 を出力することは完全に有効です。スレッド A での操作の順序は、スレッド A で実行されているコードから観察できる必要があるだけです。スレッド B は、状態の任意の組み合わせを確認できます。x と y への書き込みは、実際には順番どおりに行われない場合があります。

これは、ほとんどの人がこの種の並べ替えを予期していないシングル プロセッサ システムでも発生する可能性があります。コンパイラによって並べ替えが行われる場合があります。SMP では、コンパイラーが順序を変更しなくても、メモリー書き込みが別々のプロセッサーのキャッシュ間で順序変更される場合があります。

それがあなたの答えにならないようであれば、質問にあなたの例の詳細を含めてください。変数を使用しないと、そのような使用法が安全かどうかを明確に判断することはできません。

于 2008-09-16T14:01:06.550 に答える
3

それは、そのステートメントによって実際に行われた作業に依存します。たとえば、C++ クラスが = 演算子をオーバーロードし、そのステートメント内で自明でないことを行った場合などです。

私は誤って POD 型 (組み込みのプリミティブ型) でこのようなことを行うコードを書いてしまいましたが、うまくいきました。

この変数を使用するときに、メモリをこの変数の周りにロックしないのはなぜですか? 実際、これがコードのある時点で発生する可能性がある唯一の書き込みステートメントであることがどういうわけか「わかっている」場合、値 11 を共有変数に書き込む代わりに直接使用しないのはなぜでしょうか? (編集:コード内で直接マジック ナンバー11を使用する代わりに、定数名を使用する方がよいと思います。)

これを使用して、少なくとも 1 つのスレッドがいつこのステートメントに到達したかを把握する場合は、1 から始まり、最初にヒットしたスレッドによって減分されるセマフォを使用できます。

于 2008-09-16T13:34:19.703 に答える
1

一般に、これは、システムが不可分操作(単一サイクルで実行されることが保証されている操作)を提供しない限り、安全な方法とは見なされません。その理由は、「C」ステートメントは単純に見えますが、多くの場合、基礎となるアセンブリ操作が多数行われているためです。

OSに応じて、実行できることがいくつかあります。

  • アクセスを保護するために相互排除セマフォ(ミューテックス)を使用する
  • 一部のOSでは、プリエンプションを一時的に無効にすることができます。これにより、スレッドがスワップアウトしないことが保証されます。
  • 一部のOSは、単純な古いミューテックスよりもパフォーマンスの高いライターまたはリーダーのセマフォを提供します。
于 2008-09-16T13:43:43.643 に答える
1

これが私の質問に対する見解です。

変数に書き込む 2 つ以上のスレッドが実行されています...ステータス フラグなどのように、それらの 1 つ以上が true かどうかだけを知りたい場合。次に、コードの別の部分(スレッドが完了した後)で、少なくともスレッドでそのステータスが設定されているかどうかを確認します...たとえば

bool flag = false
threadContainer tc
threadInputs inputs

check(input)
{
    ...do stuff to input
    if(success)
        flag = true
}

start multiple threads
foreach(i in inputs) 
   t = startthread(check, i)
   tc.add(t)  // Keep track of all the threads started

foreach(t in tc)
    t.join( )  // Wait until each thread is done

if(flag)
   print "One of the threads were successful"
else
   print "None of the threads were successful"

どのスレッドがステータスをtrueに設定したかわからない場合は、上記のコードで問題ないと思います。すべてのマルチスレッド処理が完了するまで待ってから、そのフラグを読み取ることができます。私は間違っているかもしれません。

于 2008-09-21T17:24:30.707 に答える
1

結果は未定だと思います。コンパイラごとに、言語ごとに、OSごとに、OSごとに異なるため、安全ではありません。

ただし、なぜこれを行う必要があるのでしょうか。ミューテックス ロックを取得する行を追加するのは、(ほとんどの言語で) 1 行または 2 行のコードだけであり、問​​題の可能性を排除します。これに 2 つの費用がかかる場合は、問題を解決する別の方法を見つける必要があります。

于 2008-09-16T13:27:42.170 に答える
-1

操作がアトミックである場合は、問題なく実行できるはずです。しかし、私は実際にはそれをしません。オブジェクトのロックを取得して値を書き込むだけの方が適切です。

于 2008-09-16T13:44:37.870 に答える
-1

プロパティが 11 以外に割り当てられることはないと仮定すると、そもそも割り当ての理由がわかりません。それを定数にしてください。

代入は、代入の行為自体に他の副作用がない限り、値を変更する場合にのみ意味があります。たとえば、揮発性書き込みには Java のメモリ可視性に関する副作用があります。また、複数のスレッド間で共有される状態を変更する場合は、同時実行の問題を同期または「処理」する必要があります。

適切な同期を行わずに、複数のスレッド間で共有される状態に値を割り当てると、他のスレッドがその変更をいつ確認するかについての保証はありません。また、可視性が保証されないということは、他のスレッドが割り当てられたものを見ることができない可能性があることを意味します。

コンパイラ、JIT、CPU キャッシュ。彼らは皆、あなたのコードをできるだけ速く実行させようとしています。あなたがメモリの可視性を明示的に要求しなければ、彼らはそれを利用します。あなたのマシン上にない場合は、他の誰か。

于 2008-09-21T13:28:16.220 に答える