目录
1.现象和原因分析
2. 总结
1.现象和原因分析
就在最近的开发过程中,程序一运行在控制台就打印:
Qt: Dead lock detected while activating a BlockingQueuedConnection:
咋一看,怎么出现死锁了呢?仔细看下,找到信号槽关联的地方:
QObject::connect(AppLayerDataProcCenter::getInstance().get(), &AppLayerDataProcCenter::sigOfPushParamToHardwareFromApp,HardwareDataProcCenter::getInstance().get(), &HardwareDataProcCenter::onSlotOfPushParamToHardwareFromApp, Qt::BlockingQueuedConnection);
QObject::connect的最后一个参数使用Qt::BlockingQueuedConnection,翻看Qt的源码(5.12.12)搜索上面控制台打印的错误,显示:
。。。else if (c->connectionType == Qt::BlockingQueuedConnection) {if (receiverInSameThread) {qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: ""Sender is %s(%p), receiver is %s(%p)",sender->metaObject()->className(), sender,receiver->metaObject()->className(), receiver);}QSemaphore semaphore;QMetaCallEvent *ev = c->isSlotObject ?new QMetaCallEvent(c->slotObj, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore) :new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore);QCoreApplication::postEvent(receiver, ev);locker.unlock();semaphore.acquire();locker.relock();continue;。。。
BlockingQueuedConnection是DirectConnection和QueuedConnection的混合体。与DirectConnection一样,参数可以保留在堆栈上,因为堆栈位于被阻塞的线程上。无需复制参数。与QueuedConnection一样,一个事件被投递到另一个线程的事件循环中。该事件还包含一个指向QSemaphore的指针。传递事件的线程将在调用插槽后立即释放信号量。同时,调用信号的线程将获取信号量,以便等待事件处理完毕。
当您需要在另一个线程中调用函数并在其完成前等待结果时,BlockingQueuedConnection对于实现线程通信非常有用。然而,使用时必须谨慎。
因此,使用BlockingQueuedConnection阻塞模式的信号和槽必须是两个不同的线程,如果是一个线程,QCoreApplication::postEvent给当前线程投递一个事件,就一直等不到回应,线程被挂起,从而导致死锁。
2. 总结
Qt::BlockingQueuedConnection
是一种信号与槽之间的连接类型,它用于在Qt的事件系统中同步线程间的通信。当你使用这种类型的连接时,发射信号的线程会阻塞,直到接收信号的槽函数执行完毕。这种机制在需要确保信号发射后某些操作必须完成的情况下非常有用,但它也可能导致死锁或性能问题,特别是当多个线程相互等待时。
使用场景
- 线程间同步:当你需要在不同线程间同步操作,并且希望确保一个线程的操作在另一个线程继续之前完成。
- 确保顺序执行:在某些情况下,你可能需要确保槽函数的执行顺序与信号的发射顺序一致。
注意事项
- 死锁风险:使用
Qt::BlockingQueuedConnection
时,如果线程间存在循环等待或不当的锁使用,很容易发生死锁。 - 性能问题:由于发射信号的线程会阻塞,直到槽函数执行完毕,因此这可能会导致性能瓶颈,特别是在涉及多个线程和复杂操作时。
- 避免在GUI线程中使用:在Qt中,GUI线程(也称为主线程)通常负责处理用户输入和更新UI元素。如果在GUI线程中使用
Qt::BlockingQueuedConnection
,可能会导致UI冻结或响应变慢。
替代方案
- Qt::QueuedConnection:这是非阻塞的连接类型。发射信号的线程不会等待槽函数的执行,而是立即返回。槽函数将在接收信号的线程的事件队列中排队执行。
- Qt::DirectConnection:如果信号和槽在同一个线程中,使用这种连接类型可以直接调用槽函数,而无需通过事件队列。
- 使用QMetaObject::invokeMethod:这个方法提供了更灵活的调用方式,包括指定连接类型、是否等待结果等。
调试和诊断
- 使用Qt的调试输出:Qt提供了丰富的调试输出选项,可以帮助你跟踪信号和槽的调用情况。
- 分析线程调用栈:使用调试器或Qt的线程调试工具来分析线程的活动状态和调用栈。
- 日志记录:在关键位置添加日志记录,以帮助你理解程序的行为和发现潜在的问题。
总之,在使用Qt::BlockingQueuedConnection
时,你需要仔细考虑其对程序性能和线程间同步的影响,并确保你的设计能够避免死锁和性能瓶颈。如果可能的话,考虑使用其他连接类型或同步机制来实现你的需求。