c++ - QSharedData 崩溃

标签 c++ multithreading qt qshareddata

在我的 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 之一的 dtor 中遇到了“删除旧的”和段错误的(实际上它是私有(private) QTimeZone 成员)。

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

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

最佳答案

QSharedData 的实例,当通过 QSharedDataPointer单独实例访问时,实际上可以同时从多个线程访问,而且很安全。共享数据指针将根据需要自动获取引用数据的拷贝。

因此,只要您在Route 对象的实例 上工作,一切都很好。您不能做的是对容器持有的元素使用引用。您可以使用对从容器持有的对象构造的临时对象的常量引用 - 即对新实例的引用。

根据 documentation :

Proper locking should be used when sharing an instance of an implicitly shared class between threads.

在不持有锁的情况下访问对隐式共享类的共享实例的引用是永远不正确的。

您必须始终有一个新实例。您可以复制构造一个临时实例,然后通过 const 引用传递它,例如作为函数调用中的参数。 Such const-references-to-temporary-instances delay the destruction of the referenced temporary object until they are not needed anymore .

这适用于 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)。

也有可能您在不相关的地方出现了一些其他内存或线程错误。

关于c++ - QSharedData 崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21538961/

相关文章:

c++ - 'class Poco::XML::Element' 没有名为 'getNodeByPath' 的成员

python - Thread.getName() 不代表实际的 worker 名称

c++ - 如何在 QCommandLineParser 中使用文件名通配符

c++ - __PRETTY_FUNCTION__ : Can a return type contain parentheses?

c++ - 绘制特定颜色的矩形 OpenCV

C++ ofstream 无法打开文件

java - 如何将数据传递到线程 System.in 以及如何从线程 System.out 获取数据?

c# - 无锁线程安全

c++ - 如何比较 2 个 QStringList 并显示结果?

c++ - 将 void(__cdecl MyClass::*)() 转换为 void * 时出错