0

マルチスレッド Windows アプリケーションを作成しました。ここで、スレッド:
A – ユーザーの操作を処理し、B からのデータを処理する Windows フォームです
。B – 時々データを生成し、2 つの A に渡します。

スレッド セーフ キューは、スレッド B から A にデータを渡すために使用されます。エンキューおよびデキュー機能は、Windows クリティカル セクション オブジェクトを使用して保護されます。

エンキュー関数が呼び出されたときにキューが空の場合、関数は PostMessage を使用して、キューにデータがあることを A に伝えます。この関数は、PostMessage への呼び出しが正常に実行されていることを確認し、成功していない場合は PostMessage を繰り返し呼び出します (PostMessage はまだ失敗していません)。

これは、ある特定のコンピューターが時折メッセージを失い始めるまで、かなり長い間うまくいきました。失うとは、PostMessage が B で正常に返されるが、A がメッセージを受信しないことを意味します。これにより、ソフトウェアがフリーズしたように見えます。

私はすでにいくつかの許容できる回避策を考え出しました。Windows がこれらのメッセージを失っている理由と、これが 1 台のコンピューターでのみ発生している理由を知るのは興味深いことです。

コードの関連部分を次に示します。

// Only called by B
procedure TSharedQueue.Enqueue(AItem: TSQItem);
var
 B: boolean;
begin
  EnterCriticalSection(FQueueLock);
  if FCount > 0 then
    begin
      FLast.FNext := AItem;
      FLast := AItem;
    end
  else
    begin
      FFirst := AItem;
      FLast := AItem;
    end;

  if (FCount = 0) or (FCount mod 10 = 0) then // just in case a message is lost
    repeat
      B := PostMessage(FConsumer, SQ_HAS_DATA, 0, 0);
      if not B then 
  Sleep(1000); // this line of code has never been reached
    until B;

  Inc(FCount);
  LeaveCriticalSection(FQueueLock);
end;

// Only called by A 
function TSharedQueue.Dequeue: TSQItem;
begin
  EnterCriticalSection(FQueueLock);
  if FCount > 0 then
    begin
      Result := FFirst;
      FFirst := FFirst.FNext;
      Result.FNext := nil;
      Dec(FCount);
    end
  else
    Result := nil;
  LeaveCriticalSection(FQueueLock);
end;

// procedure called when SQ_HAS_DATA is received
procedure TfrmMonitor.SQHasData(var AMessage: TMessage);
var
  Item: TSQItem;
begin
  while FMessageQueue.Count > 0 do
    begin
      Item := FMessageQueue.Dequeue;
      // use the Item somehow
    end;
end;
4

3 に答える 3

3

FCountによっても保護されていFQueueLockますか?FCountそうでない場合は、投稿されたメッセージが既に処理された後にインクリメントされることに問題があります。

何が起こっている可能性があります:

  1. Bがクリティカルセクションに入る
  2. BコールPostMessage
  3. FCountA はメッセージを受信しますが、何もしません。0
  4. BインクリメントFCount
  5. Bはクリティカルセクションを残す
  6. Aはアヒルのようにそこに座っています

FCount簡単な解決策は、呼び出す前にインクリメントすることPostMessageです。

物事は予想よりも早く発生する可能性があることに注意してください (つまり、数行後に FCount をインクリメントする機会が得られる前に、PostMessage で投稿されたメッセージが別のスレッドによってキャッチされて処理されます)。スレッド環境 (複数の CPU)。そのため、「問題のマシン」に複数の CPU/コアがあるかどうかを先ほど尋​​ねました。

このような問題をトラブルシューティングする簡単な方法は、追加のログを使用してコードをスキャフォールディングし、メソッドに入るたびにログを記録し、クリティカル セクションに出入りするたびにログを記録することです。その後、ログを分析して、イベントの実際の順序を確認できます。

別の注意として、このようなプロデューサー/コンシューマーのシナリオで実行できるちょっとした最適化は、1 つではなく 2 つのキューを使用することです。コンシューマが起動して完全なキューを処理する場合、完全なキューを空のキューと交換し、完全なキューをロック/処理するだけで、2 つのスレッドが互いのキューをロックしようとせずに新しい空のキューに値を設定できます。ただし、2 つのキューのスワッピングにはまだロックが必要です。

于 2009-01-15T03:25:01.763 に答える
1

エンキュー関数が呼び出されたときにキューが空の場合、関数はPostMessageを使用して、キューにデータがあることをAに通知します。

キューのサイズを確認して発行する前に、メッセージキューをロックしていますPostMessageか?実際にAが最後のメッセージを処理していて、アイドル状態になりそうなときに、キューをチェックして空でないことがわかる競合状態が発生している可能性があります。

実際に競合状態が発生していて、問題がないかどうかを確認するにはPostMessage、イベントの使用に切り替えることができます。ワーカースレッド(A)は、メッセージを待つのではなく、イベントを待ちます。Bは、メッセージを投稿する代わりに、そのイベントを設定するだけです。

これは、特定の1台のコンピューターが時折メッセージを失い始めるまで、かなり長い間うまく機能していました。

万が一、この特定のコンピューターのCPUまたはコアの数は、問題がない他のコンピューターとは異なりますか?シングルCPUマシンから複数の物理CPU/コアを備えたマシンに切り替えると、新しい競合状態またはデッドロックが発生する場合があります。

于 2009-01-14T22:11:06.700 に答える
-1

2 番目のインスタンスが無意識のうちに実行してメッセージを食べ、処理済みとしてマークする可能性はありますか?

于 2009-01-14T21:57:07.757 に答える