2014-03-12 45 views
0

我是線程新手,我是一名初級開發人員:)所以我猜測有很多錯誤。我的情況是這樣的:我的線程設計是好還是不好?

  • 看看數據庫如果threre是DATAS女巫將sended採取這些DATAS
  • 添加此DATAS到隊列
  • 如果隊列不空出隊下一mesaj,並將其發送
  • 並等待10秒MSJ從另一個線程,
  • 如果MSJ來到停下來等待,並傳遞給下一個MSG隊列
  • 如果MSJ didnt來嘗試送等待它10秒2再次倍
  • 如果仍然沒有消息傳遞到下一個消息直到消息完成。
  • 然後再次看向數據庫中的MSG

我試圖做到這一點是這樣的:

private Thread ReceiveThread; 
    private Thread SendThread; 
    internal static Thread ServiceThread; 

這3個線程

ReceiveThread = new Thread(ReceiveTask); 
    ReceiveThread.Start(); 
    ServiceThread = new Thread(SerAutoThread.SendServiceMsg); 
    ServiceThread.Start(); 
    SendThread = new Thread(SendTask); 
    SendThread.Start(); 

..

class SerAutoThread 
    { 
    internal static object[] NextService; 
    public static readonly object _locker = new object(); 
    internal static Queue<object[]> Services; 
    internal static int sendingTime = 0; 
    private static DatabaseFirebird DB; 
    internal static void SendServiceMsg() 
    { 
     DB = new DatabaseFirebird(); 
     DB.Open(ConnectionStr); 
     Services = new Queue<object[]>(); 
     while (true) 
     { 
      if (Services.Count != 0) 
      { 
       SetNextSerAndSend(); 
      } 
      else 
      { 
       CheckAndSetServices(); 
      } 
     } 
    } 


    private static void SetNextSerAndSend() 
    { 
     NextService = Services.Dequeue(); 
     for (int j = 0; j < 4; j++) 
      { 
       if (sendingTime == TRANSMITTED) 
       { 
        //pass to next msg 
        sendingTime = 0; 
        j = NEXTMSG; 
       } 
       else if (sendingTime < 3) 
       { 
        sendingTime++; 
        Byte[] data = SetNextPckage(); 
        DeviceManager.MessageSendQueue.PostItem(new SendMessage("UDPCmd", 
       NextService[(int)NextMsg.DeviceId].ToString(), 
       data, data.Length)); 
        MyDebug.WriteLine("Sended..."); 
        lock (_locker) 
        { 
         Monitor.Wait(_locker, TimeSpan.FromSeconds(10)); 
        } 
       } 
       else 
       { 
        // pass to next msg 
        j = NEXTMSG; 
       } 
      } 

    } 
} 

..

private void ReceiveTask() 
     { 
      ReceiveMessage receiveMsg; 
      while (true) 
      { 
       receiveMsg = Com.MessageReceiveQueue.GetItem(-1); 
       SerAutoThread.sendingTime 
         = SerAutoThread.TRANSMITTED; 
        lock (SerAutoThread._locker) 
        { 
         Monitor.Pulse(SerAutoThread._locker); 
        } 

      } 
     } 

...

private void SendTask() 
     { 
      SendMessage msg; 
      while (true) 
      { 
       msg = MessageSendQueue.GetItem(-1); 
       String rtrn = PushData(msg); 
      } 

     } 

是線程安全與否。我不確定設計是否有問題,或者我在別處做錯了什麼? 謝謝...

+3

您可以看看TPL,Reactive Extensions或TPL-Dataflow。在那裏你可以找到安全的方法。考慮異常處理等。看看這些庫,並使用它們來解決您的問題。 – embee

+1

我認爲這屬於更多的http://codereview.stackexchange.com/? – Jordy

回答

1

它看起來像你錯過了一些角落的情況下,你可以得到比賽條件。例如,SerAutoThread可能會寫入一個包到MessageSendQueue,即SerAutoThread甚至開始等待之前立即確認(不太可能但可能)。這隻會造成意想不到的延遲,而不會造成故障。

但是,另一個角落案例是,如果ReceiveTask收到確認後SerAutoThread已放棄等待並已發送下一條消息。在這種情況下,SerAutoThread會認爲ReceiveTask剛剛確認新消息時才確認前一個消息。您可能需要爲您的郵件提供ID以防止發生這種情況,以便您可以準確地確定哪些郵件正在被確認。

編輯:根據您的評論,我正在查看代碼,並着眼於是否存在死鎖或活鎖的風險。我假設你的MessageSendQueueMessageReceiveQueue是阻塞生產者 - 消費者風格隊列的實例,類似於基於方法名稱和參數的this one。我還會假設這些線程並沒有因爲例外而被殺害,因爲(我衷心希望)你會注意到這一點。

讓我們從SendThread開始,因爲它是最容易分析的;從線程的角度來看,基本上沒有任何問題可以解決,儘管有一種方法可以徹底關閉它。只要東西被髮布到隊列中(並且PushData不會以某種方式阻塞),該線程將最終發送它。

ReceiveThread遵循從隊列中消耗項目的相同(良好,安全)模式,但它也通過共享變量和監視器與ServiceThread進行通信 - 不太安全和非常低級別。假設我們看到所有對_locker對象的引用,這裏就沒有死鎖的風險,因爲沒有任何代碼在保持_locker鎖的同時等待其他任何代碼。總之,只要消息在隊列中可用,該線程也將繼續執行它的操作。

但是,像您一樣設置sendingTime是數據競爭,並可能導致ServiceThread中的意外行爲。這是因爲對sendingTime的更改可能隨時發生,例如,在檢查if(sendingTime < 3)和下一行的增量之間,留下TRANSMITTED + 1。還有其他古怪的事情可能發生。當你從兩個線程訪問同一個變量時,你總是需要確保有適當的同步。

但是這可能導致ServiceThread鎖定?假設2 < NEXTMSG < int.MaxValue,我真的不知道如何。在丟棄當前Service之前,SetNextSerAndSend()中的循環將最多運行四次,並且每次運行最多等待10秒,所以它應該始終向前。

看來我們仍然可以進入一種情況,即不再有任何有用的事情發生了。如果發送時間變成既不低於3也不發送的值,它似乎不再被設置。 SetNextSerAndSend()中的循環將始終執行else分支並立即跳至下一條消息。在我看來,sendingTime必須在else分支中重置爲0以防止出現這種情況。請注意,這將允許發送再次前進,但在將所有訪問同步到sendingTime之前,您的程序將不會線程安全。

+0

actualy我給我的消息id。這不是我的整個代碼(可以理解)。當我運行這個應用程序時,這工作正常,昨天只是我的數據庫管理系統(Firebird)所說的。然後我停止了我的應用程序,停止了firebird服務器,然後再次啓動並再次運行我的應用程序現在它再次確定。我沒有深入使用線程。所以我很擔心線程安全。線程鎖定的火鳥是否可行? – kudra

+0

我把我的回答擴展了很多 - 對不起,如果它非常冗長,但我在試圖分析你的代碼的同時寫作,另外我累了。但是,我可能已經發現了代碼如何活鎖(即繼續運行而沒有死鎖,但從未做過任何有用的事情)。 – Medo42

+0

非常感謝你。非常有用和說明.. – kudra