6

この質問について疑問に思っていたとき、私が設計しているアプリケーションの競合状態について心配していました。

プログラムの1つのコンポーネントによって管理される、ある種の大きな配列またはコレクションがあるとしましょう。そのコンポーネントをMonitorと呼びましょう。その仕事は、コレクションが「ダーティ」であるかどうか、つまり最近変更されたかどうかを定期的にチェックし、変更された場合は、スナップショットをディスクに書き込み(これは、クラッシュが発生した場合にアプリケーションをチェックポイントすることです)、再度クリーンとしてマークすることです。

別のスレッドで実行されている同じプログラムの他のコンポーネントは、モニターのメソッドを呼び出して、配列/コレクションにデータを追加したり、データを変更したりします。これらのメソッドは、コレクションをダーティとしてマークします。

さて、変更メソッドは他のコンポーネントのスレッドで実行されますよね?そして、運が悪ければ、スナップショットがディスクに書き込まれているときに呼び出され、すでに書き込まれているデータを変更し、ダーティフラグを設定すると、その直後にモニターのスレッドが変更を保存せずに設定を解除する可能性があります(それが変更されたとき、すでに要素を過ぎていました)。だから私はきれいとしてマークされている汚れたコレクションを持っています。

しばらくの間、コレクションの一時的なコピーを作成し、それをクリーンにマークしてから、コピーをシリアル化することで問題を解決できるのではないかと思いました。しかし、コピーはアトミックでしょうか。つまり、コピー中にコレクションが変更されないことを確認できますか?

その間、私は次のようなより良い解決策を見つけたと思います

  • ディスクへの書き込みを開始する前にロックフラグを設定し、フラグが設定解除されるまでデータ変更メソッドを待機させます
  • データ変更メソッドをコレクションに直接ではなく「変更キュー」に書き込み、そのキューのディスク書き込みプロセスを実行するスレッドを使用する

そして、ロックフラグが最善の方法かもしれないと思います。しかし、私はまだ興味があります:可変アトミックをコピーしていますか?


フォローアップ:これはそれ自体の問題になるかもしれませんが、実際にはほとんど同じです。以下の回答によると、私の「ロックフラグ」アプローチも機能しない可能性がありますよね?データ変更メソッドは、「ロックされた」値に設定されているときにロックフラグをチェックし、ロックされていないと判断する可能性があるためです。ですから、本当にこれを正しく行いたいのであれば、ミューテックスのような特別な構造が必要ですよね?


私のフォローアップで彼の非常に有益な答えをくれたエリクソンに称賛を送ります。私は本当にこの2つの質問をするべきだったので、2つの答えを受け入れることができたはずです。彼も投票してください。

4

7 に答える 7

13

いいえ。たとえば、Java の long 変数は 32 ビット マシンではアトミックではありません。

さらに、「スレッド キャッシュ」の問題があります。変数が揮発性であるか、同期ブロック内にない限り、別のスレッドが変数値の変更を認識しない可能性があります。これは、long だけでなく、すべてのタイプの変数に当てはまります。

ここを読んでください:http://gee.cs.oswego.edu/dl/cpj/jmm.html、特に「原子性」と「可視性」の段落。

于 2009-01-23T14:45:55.147 に答える
6

いいえ、アトミックではありません。 その理由と対処法については、この質問を参照してください。

于 2009-01-23T14:43:03.750 に答える
2

JVM で作業する場合、他のスレッドへの変更の可視性について考慮する必要があります。一般に、割り当てはsynchronizedブロック内で行うか、変数を にするか、パッケージvolatileの変数ラッパーを使用する必要があります。java.util.concurrent.atomic

ただし、あなたの場合、「ダーティ」フラグをクリアするスレッドは 1 つだけのように思えます。つまり、データを保持するスレッドです。その場合は、データを書き込む前にフラグをクリアしてください。データの書き込み中に他のスレッドが設定した場合、次のスケジュールされた書き込みまで設定されたままになります。を使用しAtomicBooleanて、次のように、フラグのチェックとクリアの間に永続スレッドの原子性を与えます。

private final AtomicBoolean dirty = new AtomicBoolean();

/**
 * Any method that modifies the data structure should set the dirty flag.
 */
public void modify() {
  /* Modify the data first. */
  ...
  /* Set the flag afterward. */
  dirty.set(true);
}

private class Persister extends Thread {
  public void run() {
    while (!Thread.interrupted()) {
      if (dirty.getAndSet(false)) {
        /* The dirty flag was set; this thread cleared it 
         * and should now persist the data. */
         ...
      }
    }
  }
}
于 2009-01-23T15:49:14.990 に答える
1

32 ビット (少なくとも .NET では) の設定はアトミックですが、それは役に立ちません。それがロックされているかどうかを知るためにそれを読む必要があるので、それを読むかもしれません. 読んだ後、あなたが設定する前に他の誰かがそれを読むので、2つのスレッドが「保護された」コードの中に行き着きます. これはまさに、実際の同期オブジェクト (.NET Monitor クラスなど) の目的です。Interlocked を使用して、ロック変数をチェックしてインクリメントすることもできます。

参照: C# で変数にアクセスするのはアトミック操作ですか?

于 2009-01-23T14:44:44.183 に答える
-2

私が理解しているように、常にではありません。

Int32 は 2 x 32 ビットが必要なため、Int64 は 32 ビット システムにはなりません。したがって、1 つの 32 ビット セルには収まりません。

于 2009-01-23T14:45:59.267 に答える