我的线程设计是好还是不好

本文关键字:线程 我的 | 更新日期: 2023-09-27 17:49:35

我是一个新手,我是一个初级开发人员:)所以我想有很多错误。我的场景是这样的:

  • 查看数据库,如果有数据将被发送,获取这些数据
  • 将此数据添加到队列
  • 如果队列不为空,取消下一个队列并发送它
  • 和等待10秒来自另一个线程的msj,
  • 如果msj停止等待并传递到队列中的下一个消息
  • 如果msj没有出现,请重试10秒,等待2次
  • 如果仍然没有消息传递给下一个消息,直到消息发送完毕。
  • 然后再次查看数据库中的msgs

我正在尝试这样做:

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

this 3 threads

    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);
            }
        }

是否线程安全。我不确定是设计有问题还是我在其他地方做错了?谢谢…

我的线程设计是好还是不好

看起来您错过了一些可以获得竞争条件的极端情况。例如,SerAutoThread可以向MessageSendQueue写一个包,在SerAutoThread开始等待之前,这个包立即被确认(不太可能,但可能)。这只会导致意外的延迟,而不会导致故障。

然而,另一种极端情况是SerAutoThread已经放弃等待并已经发送下一个消息后,ReceiveTask收到确认。在这种情况下,SerAutoThread会认为ReceiveTask只是确认了新消息,而实际上它已经确认了前一个消息。您可能需要为消息指定id来防止这种情况发生,这样您就可以准确地知道哪条消息正在被确认。

编辑:根据你的评论,我再次查看代码,看看是否有死锁或活锁的风险。我将假设您的MessageSendQueueMessageReceiveQueue是阻塞生产者-消费者样式队列的实例,基于方法名称和参数与此实例类似。我还假定线程不是由于异常而被杀死的,因为(我真诚地希望)您已经注意到了这一点。

让我们从SendThread开始,因为它是最容易分析的;从线程的角度来看,这里基本上没有什么可能出错的地方,尽管有一种干净地关闭它的方法会很好。只要东西被发送到队列中(并且PushData没有阻塞),这个线程最终会发送它。

ReceiveThread遵循同样的(好的、安全的)从队列中消费项目的模式,但它也通过共享变量和监视器与ServiceThread通信——不那么安全,而且级别很低。假设我们看到了对_locker对象的所有引用,那么这里就没有死锁的风险,因为在持有_locker锁时没有代码等待其他任何东西。总之,只要队列中有可用的消息,这个线程也将继续做它的事情。

然而,像你这样设置sendingTime是一个数据竞争,可能会导致ServiceThread中的意外行为。这是因为对sendingTime的更改可能随时发生,例如在检查if(sendingTime < 3)和下一行的增量之间,留给您的是TRANSMITTED+1。还可能发生其他奇怪的事情。当您从两个线程访问同一个变量时,您总是需要确保有适当的同步。

但是这会导致ServiceThread锁定吗?假设2 < NEXTMSG < int.MaxValue,我真的不知道如何。在丢弃当前的Service之前,SetNextSerAndSend()中的循环将最多运行四次,并且每次运行最多等待10秒,因此它应该始终向前移动。

似乎我们仍然会陷入一种没有任何有用的事情发生的情况。如果sendingTime的值既不低于3,也不低于transmitte,那么它似乎不再被设置。SetNextSerAndSend()中的循环将始终执行else分支并立即跳转到下一条消息。在我看来,sendingTime也必须在else分支中重置为0,以防止这种情况发生。请注意,这将允许发送再次向前移动,但是在同步对sendingTime的所有访问之前,您的程序将不会是线程安全的。