我的线程设计是好还是不好
本文关键字:线程 我的 | 更新日期: 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来防止这种情况发生,这样您就可以准确地知道哪条消息正在被确认。
编辑:根据你的评论,我再次查看代码,看看是否有死锁或活锁的风险。我将假设您的MessageSendQueue
和MessageReceiveQueue
是阻塞生产者-消费者样式队列的实例,基于方法名称和参数与此实例类似。我还假定线程不是由于异常而被杀死的,因为(我真诚地希望)您已经注意到了这一点。
让我们从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
的所有访问之前,您的程序将不会是线程安全的。