C# 我的线程设计是否正常?
我是线程方面的新手,我是一名初级开发人员:),所以我猜有很多错误。我的设想是:C# 我的线程设计是否正常?,c#,multithreading,thread-safety,C#,Multithreading,Thread Safety,我是线程方面的新手,我是一名初级开发人员:),所以我猜有很多错误。我的设想是: 查看数据库,如果这些数据将被发送,则获取这些数据 将此数据添加到队列中 如果队列不为空,则下一个mesaj退出队列并发送 等待10秒,从另一个线程发送msj 如果msj停止等待并传递到队列中的下一个消息 如果msj未出现,请尝试再次发送并等待10秒2次 如果仍然没有消息传递到下一个消息,直到msgs完成 然后再次查看数据库中的MSG 我试着这样做: private Thread ReceiveThread
- 查看数据库,如果这些数据将被发送,则获取这些数据
- 将此数据添加到队列中
- 如果队列不为空,则下一个mesaj退出队列并发送
- 等待10秒,从另一个线程发送msj
- 如果msj停止等待并传递到队列中的下一个消息
- 如果msj未出现,请尝试再次发送并等待10秒2次
- 如果仍然没有消息传递到下一个消息,直到msgs完成
- 然后再次查看数据库中的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();
它是否线程安全。我不确定是设计出了问题,还是我在其他地方做错了?
谢谢…看起来你错过了一些角球案例,你可以获得比赛条件。例如,
SerAutoThread
可以向MessageSendQueue
写入一个包,在SerAutoThread
甚至开始等待之前(不太可能,但可能)会立即确认该包。这只会导致意外延迟,而不会导致故障
但是,另一种情况是,如果ReceiveTask
在SerAutoThread
已放弃等待并已发送下一条消息之后收到确认。在这种情况下,SerAutoThread
会认为,ReceiveTask
在确认前一条消息时,只是确认了新消息。您可能需要为您的消息提供ID以防止这种情况发生,这样您就可以准确地知道哪条消息正在被确认
编辑:根据您的评论,我将再次查看代码,以确定是否存在死锁或活锁风险。我假设您的MessageSendQueue
和MessageReceiveQueue
是阻塞生产者-消费者样式队列的实例,类似于基于方法名称和参数的队列。我还假设线程不是由于异常而被终止的,因为(我真诚地希望)您会注意到这一点
让我们从SendThread
开始,因为它最容易分析;从线程的角度来看,这里基本上没有什么会出错的地方,尽管有一种方法可以干净地关闭它会很好。只要将内容发布到队列中(并且PushData
不会以某种方式阻塞),此线程最终将发送它
ReceiveThread
遵循从队列中消费项目的相同(良好、安全)模式,但它也通过共享变量和监视器与ServiceThread
通信-不太安全且级别很低。假设我们看到了对\u locker
对象的所有引用,这里没有死锁的风险,因为没有代码在持有\u locker
锁时等待任何其他操作。总之,只要队列中有消息可用,该线程也将继续执行其任务
但是,像您这样设置sendingTime
是一种数据竞争,可能会导致ServiceThread
中出现意外行为。这是因为对sendingTime
的更改可能随时发生,例如,在检查if(sendingTime<3)
和下一行的增量之间,留给您的是+1。还有其他一些古怪的事情可能会发生。当您从两个线程访问同一个变量时,始终需要确保有适当的同步
但是这会导致ServiceThread
锁定吗?假设2
,我真的不知道怎么做。SetNextSerAndSend()
中的循环在丢弃当前的服务之前最多运行四次,每次运行最多等待10秒,因此它应该一直向前运行
看来,我们仍然可以进入一种情况下,没有什么有用的事情可以发生了。如果sendingTime成为既不低于3也不传输的值,则它似乎不再被设置。SetNextSerAndSend()
中的循环将始终执行else分支并立即跳到下一条消息。在我看来,else分支中的sendingTime
也必须重置为0才能防止出现这种情况。请注意,这将允许发送再次向前移动,但在同步对sendingTime
的所有访问之前,您的程序将不会是线程安全的。您可以查看TPL、反应式扩展或TPL数据流。在那里你可以找到安全的方法。考虑异常处理等。看看这些库,并使用它们来解决您的问题。我认为这更适合于?实际上,我给我的MSG提供了id。这不是我的全部代码(为了理解)。当我运行这个应用程序时,它工作得很好,昨天只是我的数据库管理系统(Firebird)出现了故障。然后我停止我的应用程序,停止firebird服务器,然后再次启动并再次运行我的应用程序。现在又好了。我使用的线程不是非常不正确的。所以我担心线程安全。可能是线程锁定的firebird吗?我扩展了我的答案,很抱歉,如果它很冗长,但我在编写代码的同时,试图分析您的代码,加上我很累。然而,我可能已经发现了您的代码是如何实现livelock的(也就是说,在没有死锁的情况下继续运行,但不再做任何有用的事情)。
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);
}
}