2010-05-08 21 views
1

我遇到了實現簡單工作隊列的麻煩。做一些分析,我面臨一個微妙的問題。工作隊列由常規鏈接列表支持。代碼看起來是這樣的(簡化):實施簡單工作隊列的問題

0. while (true) 
    1. while (enabled == true) 
     2. acquire lock on the list and get the next action to be executed (blocking operation) (store it in a local variable) 
     3. execute the action (outside the lock on the list on previous line) 
    4. get lock on this work queue 
     5. wait until this work queue has been notified (triggered when setEnabled(true) has been callled) 

所述的setEnabled(e)中的操作是這樣的(簡化):

enabled = e 
if (enabled == true) 
    acquire lock on this work queue and do notify() 

雖然此方法有效,存在其中發生死鎖的情況。它發生在以下罕見的情況:在步驟(3)在正在執行的行動,的setEnabled

  • (假)被稱爲
  • 之前(4)輸入步驟,的setEnabled(真)被稱爲
  • 現在步驟(5)繼續等待永遠,因爲這個工作隊列已經被通知,但我們錯過了

如何解決這個問題?我一直在看這個一段時間,但我無法想出一個解決方案。

請注意我對線程同步相當陌生。

非常感謝。

+0

您正在使用特定語言嗎?例如,.NET語言已經完成了大部分工作。 – ChaosPandion 2010-05-08 20:23:00

+0

我在只支持Java 1.1的移動設備上使用Java進行此操作。我不僅對解決方案感興趣(因爲我可以輕鬆地谷歌和下載一些圖書館),但我想知道我做錯了什麼。我從來沒有真正深入過線,我認爲這是一個提高自己的好機會。 – 2010-05-08 20:25:57

回答

0

如果您認爲問題是遺漏通知,那麼只需存儲另一個布爾標誌,例如「wasNotified」。然後在第5步中,執行wasNotified的同步檢查。如果您收到通知,請重複步驟1,否則請等待另一個通知。

您也可以使用condition variable來實現此目的(儘管如果限制爲1.1,Condition類可能不適用於您)。

1

不確定多線程內存模型如何在移動Java上。對於桌面Java,我很肯定它有一些嚴重的錯誤,一直到Java 1.5。

會更容易有超過僞代碼真正的Java代碼來解決..但我也不會感到驚訝,如果這是一個Java 1.1的bug,而不是一個代碼錯誤...

0

我認爲有以下2個變化將解決死鎖蟲:

1)變化的setEnabled如下:

setEnabled(e){ 
    acquire lock on Q{ 
     enabled = e 
     if (enabled == true) 
      do Q.notify() 
    } 
} 

2)添加條件到步驟5:

如果已啓用=假,等到此工作隊列已通知(Q.wait())

此方法保證只有啓用標誌當前處於關閉狀態纔會發生步驟5的等待。

+0

我瞭解第二步。但是你爲什麼把賦值'enabled = e'放入鎖中?我無法找到爲什麼這是必要的。 – 2010-05-09 09:59:43

+0

@John:對於死鎖情況並不是必須的,但它可以正確地同步訪問'enabled'標誌。如果多個線程讀/寫一個變量,那麼訪問應該在同一個鎖上同步,或者變量應該聲明爲volatile。否則,可能會出現線程可見性問題。 – 2010-05-10 07:09:06

0

您應該確保您檢查並等待在一個循環的條件,否則你可能會過早地取消等待,並可能導致死鎖以後:

改變這一點:

if (! condition) 
{ 
    wait(); 
} 

對此:

while (! condition) 
{ 
    wait(); 
} 

這是直接來自wait的文檔。

+0

感謝您的指點,我仍然不明白爲什麼,但我會做一些研究來找出答案。 – 2010-05-09 09:58:57

0

您正嘗試創建一個可以啓用/禁用的工作人員,它與處理任務時可用的工作人員不同。您需要圍繞啓用狀態進行同步,而不是隊列。

while (true) 
    synchronize (enabled) 
     while (!enabled) 
      enabled.wait(); 
    task = taskList.take(); 
    task.run(); 

通過在同步塊之前首先執行if(!enabled)檢查,可以減少已啓用的同步(如果您願意接受可見性問題)。基本上,被稱爲雙重檢查鎖定的破壞範例。

+0

我明白爲什麼我必須在「啓用」而不是「隊列」上進行同步,但這是一個大錯誤嗎?是不是認爲'只要我在某件事上同步,這是正確的'有缺陷的想法? – 2010-05-09 10:00:48

+0

我認爲這個解決方案並不完全正確。假設隊列列表爲空,並且啓用設置爲true。這意味着函數taskList.take()被阻塞。當這個線程正在等待時,setEnabled(false)被調用。現在,一旦添加了任務,無論如何都會執行任務,因爲啓用標誌被忽略。 – 2010-05-09 10:02:20

+0

@John,你在原文中的實現存在着同樣的缺陷。所以我認爲這是可以接受的/打算一旦啓用,工作人員至少執行一項任務。另外,是的,您可以同步任何內容,但使用隊列進行同步意味着啓用/禁用必須等待列表中的添加/獲取操作。 – 2010-05-09 17:29:08