2014-03-12 50 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