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;
}

「delete old」を押して、dtor の 1 つQDateTime(実際にはプライベートQTimeZoneメンバー) で seg faulting を実行しています。

私の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 に答える