我写了一个多线程的windows应用程序,其中thread:
A - 是一个窗体,用于处理用户交互并处理来自B的数据
B - 偶尔生成数据并将其传递两个A.
线程安全队列用于将数据从线程B传递到A.使用Windows关键节对象保护入队和出队函数。
如果在调用enqueue函数时队列为空,则该函数将使用PostMessage告诉A队列中有数据。该函数检查以确保成功执行对PostMessage的调用,并在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;
答案 0 :(得分:3)
FCount
是否也受FQueueLock
保护?如果没有,那么问题在于FCount
在已发布消息已经处理后递增。
以下是可能发生的事情:
PostMessage
FCount
为0
FCount
快速补救措施是在致电FCount
之前增加PostMessage
。
请记住,事情可能比预期更快发生(即PostMessage发布的消息被其他线程捕获并处理,之后您有机会在几行之后增加FCount),尤其是当您处于真正的多线程环境(多个CPU)。这就是为什么我之前问过“问题机器”是否有多个CPU /核心。
解决这些问题的一种简单方法是,每次输入方法,输入/离开关键部分等时,都需要使用附加日志记录来编写代码,然后您可以分析日志以查看事件的真实顺序。
另外,在这样的生产者/消费者场景中可以完成的一个很好的小优化就是使用两个队列而不是一个队列。当消费者醒来处理完整队列时,您将整个队列与空队列交换,只需锁定/处理完整队列,同时可以填充新的空队列,而不会让两个线程试图锁定彼此的队列。但是,在交换两个队列时你仍然需要一些锁定。
答案 1 :(得分:1)
如果排队时队列为空 调用函数,函数会 使用PostMessage告诉A那里 是队列中的数据。
在检查队列大小并发出PostMessage
之前,您是否锁定了消息队列?您可能遇到竞争状况,在这种情况下您检查队列并发现它非空,而实际上A正在处理最后一条消息并且即将闲置。
要查看您是否确实遇到了竞争条件且PostMessage
没有问题,您可以切换到使用某个事件。工作线程(A)将等待事件而不是等待消息。 B只会设置该事件而不是发布消息。
这种方法运作了很长时间 直到一台特定的计算机开始 失去偶尔的消息。
任何机会,这台特定计算机的CPU或核心数量是否与您认为没有问题的其他计算机不同?有时,当您从单CPU机器切换到具有多个物理CPU /核心的机器时,可能会出现新的竞争条件或死锁。
答案 2 :(得分:-1)
是否有第二个实例在不知不觉中正在运行并吃掉消息,将其标记为已处理?