我的Qt 5.2.0应用程序中有一个不常见但相当一致的崩溃,我有一段时间的诊断,但相信与之相关QSharedData
.该应用程序是高度多线程的,这可能是问题的一部分.
有问题的课程在这里:
class RouteData : public QSharedData { public: RouteData() : m_destAddress(0), m_valid(false), m_movingAverage(ROUTE_INITIAL_QUALITY) { } RouteData(const RouteData &other) : QSharedData(other), m_destAddress(other.m_destAddress), m_addresses(other.m_addresses), m_valid(other.m_valid), m_movingAverage(other.m_movingAverage), m_lastSuccess(other.m_lastSuccess), m_lastFailure(other.m_lastFailure) { } ~RouteData() { } quint16 m_destAddress; QListm_addresses; bool m_valid; double m_movingAverage; QDateTime m_lastSuccess; QDateTime m_lastFailure; }; class Route { public: Route() { d = new RouteData; } Route(quint16 destAddress) { d = new RouteData; d->m_destAddress = destAddress; } Route(const Route &other) : d(other.d) {} QString toString() const; bool isValid() const { return d->m_valid; } quint16 destAddress() const { return d->m_destAddress; } QList addressList() const { return d->m_addresses; } quint32 length() const { return d->m_addresses.length(); } double quality() const { return d->m_movingAverage; } quint64 msecsSinceLastFailure() const; void setDestination(quint16 dest) { d->m_destAddress = dest; } void setAddressList(const QList &addressList); void markResult(bool success); bool operator<(const Route& other) const; bool operator==(const Route& other) const; bool operator!=(const Route& other) const; private: QSharedDataPointer d; }; Q_DECLARE_TYPEINFO(Route, Q_MOVABLE_TYPE);
崩溃发生在此处,将路径插入到QMap
:
SendResult EmberGateway::ezspSendUnicast(quint16 indexOrDestination, ..., const Route& route) { ... if (route.isValid()) { m_lastRouteMap.insert(indexOrDestination, route);
其中m_lastRouteMap是一个QMap
.
堆栈跟踪看起来像这样:
(gdb) where #0 0x00007fa297ced9a8 in QTimeZone::~QTimeZone() () from /usr/local/Trolltech/Qt-5.2.0/lib/libQt5Core.so.5 #1 0x00007fa297c96de5 in QDateTime::~QDateTime() () from /usr/local/Trolltech/Qt-5.2.0/lib/libQt5Core.so.5 #2 0x00000000004644fb in RouteData::~RouteData (this=0x7fa28c00b150, __in_chrg=) at ../libILS/libILS/Route.h:33 #3 0x0000000000600805 in QSharedDataPointer ::operator= (this=0x7fa28c0e6420, o=...) at /usr/local/Trolltech/Qt-5.2.0/include/QtCore/qshareddata.h:98 #4 0x00000000005ff55b in Route::operator= (this=0x7fa28c0e6420) at ./libILS/Route.h:43 #5 0x0000000000652f8e in QMap ::insert (this=0x7fa28c0880e8, akey=@0x7fa17c6feb44: 25504, avalue=...) at /usr/local/Trolltech/Qt-5.2.0/include/QtCore/qmap.h:682 #6 0x0000000000641b4b in ils::EmberGateway::ezspSendUnicast (this=0x7fa28c088090, indexOrDestination=25504, apsFrame=..., data=..., route=...) at libILS/Gateways/EmberGateway.cpp:909 #7 0x00000000006371d5 in ils::EmberGateway::sendUnicast (this=0x7fa28c088090, destAddress=25504, array=..., route=...) at libILS/Gateways/EmberGateway.cpp:474 #8 0x00000000005fadc4 in NetworkController::sendMessageViaGateway (this=0x7fa28c03e9b0, message=...) at libILS/Controllers/NetworkController.cpp:1668 #9 0x00000000005ed8f4 in NetworkController::dispatchMessage (this=0x7fa28c03e9b0, pendingMessagePair=...) at libILS/Controllers/NetworkController.cpp:913 #10 0x0000000000604e2f in QtConcurrent::VoidStoredMemberFunctionPointerCall1 , QTimer*>, QPair , QTimer*> >::runFunctor (this=0x7fa23804ac60) at /usr/local/Trolltech/Qt-5.2.0/include/QtConcurrent/qtconcurrentstoredfunctioncall.h:410 #11 0x00000000005ff41e in QtConcurrent::RunFunctionTask ::run (this=0x7fa23804ac60) at /usr/local/Trolltech/Qt-5.2.0/include/QtConcurrent/qtconcurrentrunbase.h:132 #12 0x00007fa297c55e52 in ?? () from /usr/local/Trolltech/Qt-5.2.0/lib/libQt5Core.so.5 #13 0x00007fa297c591c2 in ?? () from /usr/local/Trolltech/Qt-5.2.0/lib/libQt5Core.so.5 #14 0x00007fa297746f3a in start_thread () from /lib64/libpthread.so.0 #15 0x00007fa296c698fd in clone () from /lib64/libc.so.6
所以在#5我们正在进行QMap :: insert,而在#4创建一个副本(通过Route"="运算符).在#3,有问题的Qt代码如下所示:
inline QSharedDataPointer& operator=(const QSharedDataPointer &o) { if (o.d != d) { if (o.d) o.d->ref.ref(); T *old = d; d = o.d; if (old && !old->ref.deref()) delete old; } return *this; }
我们正在点击"删除旧"并在其中一个人QDateTime
(实际上是私人QTimeZone
成员)中发生错误.
ezspSendUnicast()
在崩溃之前,我的方法可以运行数十万次迭代.我不认为我在泄漏记忆(根据valgrind的说法).我相信我传递给ezspSendUnicast()的Route对象是正确的互斥保护,但我可能错过了一些东西(但我认为QSharedData
在任何情况下都是线程安全的写入时复制).
任何有关如何解决这个问题的见解将不胜感激!
的情况下QSharedData
,当通过访问单独的实例中QSharedDataPointer
,就可以了,其实,在一次从多个线程访问,它是安全的.根据需要,共享数据指针将原子地获取引用数据的副本.
所以,只要你在自己的对象实例上工作Route
,一切都很好.你不能做的是使用对容器所持元素的引用.您可以对从容器保存的临时对象(即新实例)构造的临时对象使用const引用.
根据文件:
在线程之间共享隐式共享类的实例时,应使用正确的锁定.
在不持有锁的情况下访问对隐式共享类的共享实例的引用永远是正确的.
您必须始终拥有一个新实例.您可以复制构造临时实例,然后通过const引用传递它,例如作为函数调用中的参数.这样的const-references-to-temporary-instances延迟了对引用的临时对象的破坏,直到不再需要它们为止.
这适用于Qt 4和Qt 5中的所有隐式共享类 - 无论是来自Qt(所有容器!),还是来自您自己的利用设计QSharedDataPointer
.
所以,这是正确的 - 你保持自己的实例.
Route r(...); QMutexLocker l(&routeMapMutex); routeMap.insert(key, r); l.unlock(); r.setDestination(...); QMutexLocker l(&routeMapMutex); routeMap.insert(key, r); l.unlock();
就像这样 - 再次,你有自己的实例.
QMutexLocker l(&routeMapMutex); Route r = routeMap[key]; l.unlock(); if (r.isValid()) ...
但这肯定是不正确的,即使参考是常量.
QMutexLocker l(&routeMapMutex); const Route & r = routeMap[key]; l.unlock(); if (r.isValid()) ...
但这是正确的,因为它是对临时实例的const引用,其生命周期根据需要延长.因此,您引用了一个除了您有权访问的新实例.
QMutexLocker l(&routeMapMutex); const Route & r = Route(routeMap[key]); l.unlock(); if (r.isValid()) ...
这也是正确的,因为该调用由互斥锁保护:
void fun(const Route &); QMutexLocker l(&routeMapMutex); fun(routeMap[key]); l.unlock();
我的预感是,在代码中的某个位置,您可以访问对映射中值的引用(而不是const-ref为临时值)而不保留互斥锁.
也可以想象你有一些其他记忆或线程错误,它们在一个不相关的地方表现出来.