0

我寫多線程應用程序的窗口,其中線程:
A - 是一個視窗形式處理用戶交互和處理來自B.
B中的數據 - 偶爾產生數據並將其傳遞兩個APostMessage的偶爾丟失的消息

線程安全隊列用於將數據從線程B傳遞給A.使用窗口臨界段對象保護入隊和出隊函數。

如果在調用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

如果您可以提供您鎖定/解鎖/推送/彈出/檢查隊列的關鍵區域的代碼摘錄,這將有所幫助。 – 2009-01-14 22:24:28

回答

3

FCount也受FQueueLock保護?如果沒有,那麼你的問題在於FCount在發佈的消息已經被處理之後遞增。

以下是可能發生的:

  1. B進入臨界區
  2. B調用PostMessage
  3. A收到的消息,但由於FCount0
  4. 乙增量FCount
  5. 不會做任何事情
  6. B葉臨界部分
  7. A像鴨子那樣坐在那裏

快速補救措施是在撥打PostMessage之前遞增FCount

請記住,事情發生的速度可能比預期的要快(例如,PostMessage發佈的消息被其他線程捕獲並處理,然後您有機會在幾行後增加FCount),特別是當您處於一個真正的多線程環境(多個CPU)。這就是我之前詢問「問題機器」是否有多個CPU /內核的原因。

解決類似問題的簡單方法是在每次輸入方法時輸入/離開關鍵部分等腳本代碼,以便使用附加日誌記錄來記錄日誌,然後可以分析日誌以查看事件的真實順序。

在另一個說明中,可以在生產者/消費者場景中完成的一個很好的小優化就是使用兩個隊列而不是一個隊列。當消費者醒來處理完整的隊列時,您將完整的隊列替換爲空的隊列,只需鎖定/處理完整的隊列,同時可以填充新的空隊列,而不需要兩個線程試圖鎖定對方的隊列。儘管如此,您仍然需要在交換兩個隊列時進行一些鎖定。

-1

難道會有第二個實例在不知不覺中運行和吃消息,標記爲處理?

1

如果隊列爲空時,排隊 函數被調用,該函數將 使用PostMessage的告訴,有 在隊列中的數據。

在檢查隊列大小併發出PostMessage之前是否鎖定了消息隊列?當您檢查隊列並且在事實上A正在處理最後一條消息並且即將閒置時發現它不是空的情況下,您可能會遇到競爭狀況。

要查看您是否實際遇到競爭狀況而不是PostMessage問題,可以切換到使用事件。工作者線程(A)將等待事件而不是等待消息。 B會簡單地設置該事件而不是發佈消息。

這很適合於很長一段時間,直到 一個特定的計算機開始 失去偶爾的消息。

無論如何,這臺特定計算機的CPU或內核的數量是否與您沒有看到問題的其他CPU有所不同?有時,當您從單CPU機器切換到具有多個物理CPU /內核的機器時,可能會出現新的競爭條件或死鎖。

+0

您的回答對我來說沒有意義:PostMessage API發佈消息的消息隊列由O/S(不是由應用程序)控制,並且不能由應用程序「鎖定」。 – ChrisW 2009-01-14 22:16:04

+0

@ChrisW:這是OP的陳述:「使用線程安全隊列將數據從線程B傳遞給A.使用窗口臨界區對象保護入隊和出隊函數。」 – 2009-01-14 22:17:24