【问题标题】:Qt multi-thread application freezes and several threads wait for the same mutexQt 多线程应用程序冻结,多个线程等待同一个互斥锁
【发布时间】:2019-07-23 00:21:42
【问题描述】:

我在使用基于 Qt 的多线程应用程序时遇到了一个奇怪的问题。运行几天后,应用程序将冻结而没有任何响应。

发生冻结后,我可以确认包括主线程在内的多个线程处于futex_wait_queue_me 状态。当我附加到该应用程序以通过 GDB 调查线程状态时,这些线程的回溯 表明它们都在具有相同参数futex=0x45a2f8b8 <main_arena> 的以下函数处停止。

__lll_lock_wait_private (futex=0x45a2f8b8 <main_arena>)

我知道在 Linux 上,在信号处理程序中使用非异步安全函数是导致这种状态的可能原因之一,即多个线程等待同一个互斥体,(我可以从回溯确认它们都在 malloc 处停止( )/free() 相关函数调用),但在我确认我的 Qt 应用程序后,我找不到与 Linux 信号处理程序相关的实现。 (但我不确定 Qt 核心库是否在其信号/槽机制中使用 Linux 信号处理程序。)

很抱歉我不能提供这个问题的源代码,因为这是一个巨大的项目。您想告诉我一些可能导致这种现象的原因,或者关于如何调试它的一些建议吗?

提前致谢。

更新 1:

我可以提供回溯,但是很抱歉我必须删除一些敏感信息。

子线程的回溯:

#0 in __lll_lock_wait_private (futex=0x4ad078b8 <main_arena>)
#1 in __GI___libc_malloc (bytes=32) at malloc.c:2918
... ...
#11 in SystemEventImp::event(QEvent*) () 
#12 in QApplicationPrivate::notify_helper(QObject*, QEvent*) ()
#13 in QApplication::notify(QObject*, QEvent*) ()
#14 in QCoreApplication::notifyInternal(QObject*, QEvent*) ()
#15 in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) ()
#16 in QCoreApplication::sendPostedEvents (receiver=0x0, event_type=0) at kernel/qcoreapplication.cpp:1329
#17 in QWindowSystemInterface::sendWindowSystemEvents (flags=...) at kernel/qwindowsysteminterface.cpp:560
#18 in QUnixEventDispatcherQPA::processEvents (this=0x8079958, flags=...) at eventdispatchers/qunixeventdispatcher.cpp:70
#19 in QEventLoop::processEvents (this=0xbfffef50, flags=...) at kernel/qeventloop.cpp:136
#20 in QEventLoop::exec (this=0xbfffef50, flags=...) at kernel/qeventloop.cpp:212
#21 in QCoreApplication::exec () at kernel/qcoreapplication.cpp:1120
#22 in QGuiApplication::exec () at kernel/qguiapplication.cpp:1220
#23 in QApplication::exec () at kernel/qapplication.cpp:2689
#24 in main(argc=2, argv=0xbffff294)

主线程回溯:

#0 in __lll_lock_wait_private (futex=0x4ad078b8 <main_arena>) at ../ports/sysdeps/unix/sysv/linux/arm/nptl/lowlevellock.c:32
#1 in __GI___libc_malloc (bytes=8) at malloc.c:2918
... ...
#15 in QGraphicsView::paintEvent(QPaintEvent*) ()
#16 in QWidget::event(QEvent*) () 
#17 in QFrame::event(QEvent*) () 
#18 in QGraphicsView::viewportEvent(QEvent*) ()
#19 in Platform::Drawing::GraphicsView::viewportEvent(QEvent*) ()
#20 in QAbstractScrollAreaFilter::eventFilter(QObject*, QEvent*) ()
#21 in QCoreApplicationPrivate::cancel_handler(QObject*, QEvent*) ()
#22 in QApplicationPrivate::notify_helper(QObject*, QEvent*) ()
#23 in QApplication::notify(QObject*, QEvent*) ()
#24 in QCoreApplication::notifyInternal(QObject*, QEvent*) ()
#25 in QWidgetPrivate::drawWidget(QPaintDevice*, QRegion const&, QPoint const&, int, QPainter*, QWidgetBackingStore*) [clone .part.175] () 
#26 in QWidgetBackingStore::sync() ()
#27 in QWidgetPrivate::syncBackingStore() ()
#28 in QWidget::event(QEvent*) ()
#29 in QApplicationPrivate::notify_helper(QObject*, QEvent*) ()
#30 in QApplication::notify(QObject*, QEvent*) ()
#31 in QCoreApplication::notifyInternal(QObject*, QEvent*) ()
#32 in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) ()
#33 in QCoreApplication::sendPostedEvents (receiver=0x809ea50, event_type=77)
#34 in QGraphicsViewPrivate::dispatchPendingUpdateRequests (this=0x80e4418)
#35 in QGraphicsScenePrivate::_q_processDirtyItems (this=0x80de238) at graphicsview/qgraphicsscene.cpp:508
#36 in QGraphicsScene::qt_static_metacall (_o=0x80d1a80, _c=QMetaObject::InvokeMetaMethod, _id=15, _a=0x865e238)
#37 in QMetaCallEvent::placeMetaCall (this=0x898d020, object=0x80d1a80)
#38 in QObject::event (this=0x80d1a80, e=0x898d020) at kernel/qobject.cpp:1070
#39 in QGraphicsScene::event (this=0x80d1a80, event=0x898d020) at graphicsview/qgraphicsscene.cpp:3478
#40 in QApplicationPrivate::notify_helper (this=0x8077ba0, receiver=0x80d1a80, e=0x898d020) at kernel/qapplication.cpp:3457
#41 in QApplication::notify (this=0x8077970, receiver=0x80d1a80, e=0x898d020) at kernel/qapplication.cpp:2878
#42 in QCoreApplication::notifyInternal (this=0x8077970, receiver=0x80d1a80, event=0x898d020) at kernel/qcoreapplication.cpp:867
#43 in QCoreApplication::sendEvent (receiver=0x80d1a80, event=0x898d020) at ../../include/QtCore/../../src/corelib/kernel/qcoreapplication.h:232
#44 in QCoreApplicationPrivate::sendPostedEvents (receiver=0x0, event_type=0, data=0x8073318) at kernel/qcoreapplication.cpp:1471
#45 in QCoreApplication::sendPostedEvents (receiver=0x0, event_type=0) at kernel/qcoreapplication.cpp:1329
#46 in QWindowSystemInterface::sendWindowSystemEvents (flags=...) at kernel/qwindowsysteminterface.cpp:560
#47 in QUnixEventDispatcherQPA::processEvents (this=0x8079958, flags=...) at eventdispatchers/qunixeventdispatcher.cpp:70
#48 in QEventLoop::processEvents (this=0xbfffef50, flags=...) at kernel/qeventloop.cpp:136
#49 in QEventLoop::exec (this=0xbfffef50, flags=...) at kernel/qeventloop.cpp:212
#50 in QCoreApplication::exec () at kernel/qcoreapplication.cpp:1120
#51 in QGuiApplication::exec () at kernel/qguiapplication.cpp:1220
#52 in QApplication::exec () at kernel/qapplication.cpp:2689
#53 in main(argc=2, argv=0xbffff294)

更新2:

在回答这个问题的那些有价值的cmets。 我还在以下链接中分享了几个详细的回溯文件:1drv.ms/f/s!AlojS_vldQMhjHRlTfU9vwErNz-H。请参考 Readme.txt 对于一些解释和我使用的 libc 版本。 顺便说一句,当我尝试用 vfork()/waitpid() 替换 system() 时,冻结似乎不再出现。我不知道原因。

提前谢谢大家。

【问题讨论】:

  • 线程是否在等待条件变量? (例如,当队列为空时,等到其中至少有一个值来做某事)。
  • @AlexG,除了主线程之外,在其他线程中,我注册了一些事件处理程序并维护了一个事件队列,线程将无限循环。如果可以从队列中检索到事件,则将调用事件处理程序。我已经确认事件队列的弹出/推送操作具有竞争条件的互斥锁。
  • 嗯.. 无限循环确实是另一种在队列中等待的方式。在不知道代码的情况下,这是我所能得到的。
  • @AlexG,对不起,我无法理解您关于排队等候的想法,是否存在排队等候会导致这种现象的情况?你愿意给我一些细节吗?
  • @gzh 是的,这就是我要求更多回溯的原因。这方面有几个常见的应用程序和 glibc 错误。如果您不愿意公开分享所有回溯,则必须向您的供应商打开一个支持案例,否则我们无法判断是否有您可以应用的解决方法(或者错误是否存在)在应用程序中)。

标签: c linux multithreading qt glibc


【解决方案1】:

没有提供源代码,很难明确回答这个问题。根据我对多线程程序的经验,很容易忽略某些可能发生死锁的地方。在您的情况下,这听起来像是某事,这不太可能发生。但是我敢打赌,在你的代码中的某个地方你有一个潜在的死锁。

我建议您在图表中绘制整个环境,并查看哪些线程使用哪些共享资源以及互斥锁何时何地进入。

但正如我一开始所说,没有更多信息很难说。

【讨论】:

  • 感谢您的快速回答。大多数死锁涉及两个或多个不同的互斥体,我可以很容易地找到问题所在。但我不知道他们为什么要等待同一个互斥锁。
  • @gzh 对不起,不看你的代码,这将很难解决。
  • 我无法显示源代码,但我可以在这里发布回溯。我已经用回溯信息更新了我的问题。
【解决方案2】:

从追溯来看,似乎是在 Qt 尝试发布事件时调用了 malloc。

如果您尝试跨线程发送事件,Qt 可以为您排队事件。但如果这些事件没有耗尽,它们可能会填满你的记忆。然后你可以从 malloc 获得有线行为,因为没有剩余内存了。

  • 您是否有办法监控程序的内存使用情况,并查看每次内存填满时是否都会发生这种情况?
  • 您是否有办法减少系统的内存并查看此问题是否更频繁地出现?

如果确实是上面的问题,那么您可以看看这个thread 以获得解决方案。

【讨论】:

  • 感谢您的回复。当发生冻结时,我检查了内存使用情况,有足够的空闲内存,在我的应用程序中,我为消息队列设置了一个上限,如果两个多事件附加到队列中,一些事件将被丢弃。
【解决方案3】:

如果您使用信号和槽跨线程进行通信,您应该了解不同的连接模式。

  • 自动连接(默认)如果信号在线程中发出 接收对象具有亲和力,则行为相同 作为直接连接。否则,行为与 排队连接。
  • 直接连接调用槽 立即,当信号发出时。该插槽在 发射器的线程,不一定是接收器的线程。
  • Queued Connection 当控制权返回到 接收者线程的事件循环。该插槽在 接收者的线程。
  • 阻塞队列连接 插槽被调用为 对于 Queued Connection,除了当前线程阻塞,直到 插槽返回。注意:使用此类型连接同一对象 线程会导致死锁。

更多:https://doc.qt.io/archives/qt-5.6/threads-qobject.html

这个问题确实需要一些代码上下文。将数据传递到 UI 时是否会发生这种行为?如果是,您是否使用 QWidgets、QML、...?在将数据渲染到 UI 时,许多 Qt 模式都依赖于信号/槽。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-19
    • 1970-01-01
    • 2015-08-23
    • 2021-06-04
    相关资源
    最近更新 更多