4

私は、整数はスレッドセーフであるという誤った仮定で、いくつかのスレッド化コードを書きました。それらはそうですが、私の使用はスレッドセーフではないようです。スレッド数を保持するためにグローバル整数 ThreadCount を使用しています。スレッドの作成中に、ThreadCount をインクリメントします。スレッドの破棄中に、デクリメントします。すべてのスレッドの作成が完了したら、それらが完了するのを待ち (ThreadCount が 0 になるはずです)、最終レポートを作成して終了します。

ただし、ログの事後調査ですべてのスレッドが実行されて完了したことが示されている場合でも、0 にならないことがあります (5%)。したがって、すべての兆候は、ThreadCount が踏みにじられていることを示しています。これは整数なので、これは不可能だと自分に言い聞かせてきました。私は単に inc/dec を使用しています。

ここにいくつかの関連コードがあります:

var  // global
  ThreadCount : integer;            // Number of active threads
...

constructor TTiesUpsertThread.Create(const CmdStr : string);
begin
  inherited create(false);
  Self.FreeOnTerminate := true;
...
  Inc(ThreadCount);      // Number of threads created.  Used for throttling.
end;

destructor TTiesUpsertThread.Destroy;
begin
  inherited destroy;
  Dec(ThreadCount);     // When it reaches 0, the overall job is done.
end;

...
//down at the end of the main routine:

    while (ThreadCount > 0) do   // Sometimes this doesn't ever end.  
    begin
      SpinWheels('.'); // sleeps for 1000ms and writes dots... to console 
    end;

私の問題は inc/dec にあると思います。2 つ以上の dec() が同時にヒットし、両方が同じ値を読み取る衝突が発生していると思うので、同じ値に置き換えます。例: ThreadCount = 5 で、2 つのスレッドが同時に終了した場合、両方とも 5 を読み取り、4 に置き換えます。ただし、新しい値は 3 にする必要があります。

テスト環境 (異なるハードウェア、トポロジ、負荷など) ではこれが問題になることは決してないので、このソリューションをビジネス ユニットに「売り込む」前に、これが問題である可能性が高いことを確認したいと考えています。

これが私の問題である場合、重要な選択を使用して inc/dec を保護しますか?
ご覧いただきありがとうございます。

4

1 に答える 1

7

複数のスレッドが保護なしで変数を変更すると、データ競合が発生します。2 つのスレッドが同じインスタンスでインクリメントまたはデクリメントしようとすると、次のようになります。

  1. 変数がレジスタに読み込まれます。
  2. 変更はレジスターで行われます。
  3. 新しい値が変数に書き戻されます。

その読み取り/変更/書き込みはアトミックではありません。同時に 2 つのスレッドを実行している場合、正規データ競合が発生します。

  • スレッド 1 が値を読み取り、N が言う。
  • スレッド 2 は、スレッド 1 (N) によって読み取られた値と同じ値を読み取ります。
  • スレッド 1 は変数に N+1 を書き込みます。
  • スレッド 2 は変数に N+1 を書き込みます。

また、変数が 2 回インクリメントされるのではなく、1 回だけインクリメントされます。

この場合、本格的なクリティカル セクションは必要ありません。InterlockedIncrementロックフリーでスレッドセーフな変更を実行するために使用します。

于 2013-06-19T14:03:53.710 に答える