3

我在我的 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;
  QList<quint16>    m_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<quint16>  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<quint16> &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<RouteData> d;
};

Q_DECLARE_TYPEINFO(Route, Q_MOVABLE_TYPE);

崩溃发生在这里,将 Route 插入QMap

SendResult EmberGateway::ezspSendUnicast(quint16 indexOrDestination, ..., const Route& route)
{
...
    if (route.isValid()) {
        m_lastRouteMap.insert(indexOrDestination, route);

其中 m_lastRouteMap 是一个QMap<quint16, Route>.

堆栈跟踪如下所示:

(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=<optimized out>) at ../libILS/libILS/Route.h:33
#3  0x0000000000600805 in QSharedDataPointer<RouteData>::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<unsigned short, Route>::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<void, NetworkController, QPair<QSharedPointer<Message>, QTimer*>, QPair<QSharedPointer<Message>, QTimer*> >::runFunctor (this=0x7fa23804ac60)
    at /usr/local/Trolltech/Qt-5.2.0/include/QtConcurrent/qtconcurrentstoredfunctioncall.h:410
#11 0x00000000005ff41e in QtConcurrent::RunFunctionTask<void>::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<T> & operator=(const QSharedDataPointer<T> &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成员)在 dtor 中点击“删除旧”并进行分段故障。

我的ezspSendUnicast()方法在崩溃之前可以正常运行数十万次迭代。我不认为我在泄漏内存(根据 valgrind)。我相信我传递给 ezspSendUnicast() 的 Route 对象受到了适当的互斥保护,但我可能错过了一些东西(但我认为QSharedData无论如何写时复制都是线程安全的)。

任何有关如何解决此问题的见解将不胜感激!

4

1 回答 1

7

的实例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()) ...

但这肯定是不正确的,即使引用是 const。

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)。

也可以想象,您还有其他一些内存或线程错误,这些错误在不相关的地方表现出来。

于 2014-02-03T22:42:39.947 回答