0

我编写了一个多线程 Windows 应用程序,其中线程:
A – 是一个处理用户交互并处理来自 B 的数据的 Windows 窗体
。B – 偶尔生成数据并将两个 A 传递给它。

线程安全队列用于将数据从线程 B 传递到 A。入队和出队函数使用 Windows 临界区对象进行保护。

如果调用 enqueue 函数时队列为空,该函数将使用 PostMessage 告诉 A 队列中有数据。该函数检查以确保对 PostMessage 的调用成功执行,如果不成功则重复调用 PostMessage(PostMessage 尚未失败)。

这在相当长的一段时间内运行良好,直到一台特定的计算机开始丢失偶尔的消息。丢失的意思是, PostMessage 在 B 中成功返回,但 A 从未收到消息。这会导致软件出现冻结。

我已经想出了几个可接受的解决方法。我很想知道为什么 Windows 会丢失这些消息,以及为什么这只发生在一台计算机上。

这是代码的相关部分。

// 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. A 收到消息但不做任何事情,因为FCount0
  4. B 递增FCount
  5. B 离开临界区
  6. A 像鸭子一样坐在那里

一个快速的补救措施是FCount在调用PostMessage.

请记住,事情发生的速度可能比预期的要快(即在您有机会在几行之后增加 FCount 之前,使用 PostMessage 发布的消息被另一个线程捕获并处理),尤其是当您处于真正的多线程中时线程环境(多个 CPU)。这就是为什么我之前问过“问题机器”是否有多个 CPU/内核。

解决此类问题的一种简单方法是在每次输入方法、进入/离开关键部分等时使用附加日志记录代码来记录代码。然后您可以分析日志以查看事件的真实顺序。

在单独的说明中,可以在像这样的生产者/消费者场景中完成的一个很好的小优化是使用两个队列而不是一个。当消费者醒来处理完整队列时,您将完整队列与空队列交换并锁定/处理完整队列,而新的空队列可以被填充而无需两个线程尝试锁定彼此的队列。不过,您仍然需要在两个队列的交换中进行一些锁定。

于 2009-01-15T03:25:01.763 回答
1

如果调用 enqueue 函数时队列为空,该函数将使用 PostMessage 告诉 A 队列中有数据。

您是否在检查队列大小并发出之前锁定了消息队列PostMessage?您可能会遇到竞争情况,您检查队列并发现它非空,而实际上 A 正在处理最后一条消息并且即将空闲。

要查看您是否确实遇到了竞争条件而不是 的问题PostMessage,您可以切换到使用事件。工作线程 (A) 将等待事件而不是等待消息。B 将简单地设置该事件而不是发布消息。

这在相当长的一段时间内运行良好,直到一台特定的计算机开始丢失偶尔的消息。

无论如何,这台特定计算机的 CPU 或内核数量是否与您认为没有问题的其他计算机不同?有时,当您从单 CPU 机器切换到具有多个物理 CPU/内核的机器时,可能会出现新的竞争条件或死锁。

于 2009-01-14T22:11:06.700 回答
-1

是否有第二个实例在不知不觉中运行并吃掉消息,将它们标记为已处理?

于 2009-01-14T21:57:07.757 回答