У меня нечастый, но довольно постоянный сбой в моем приложении 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);
Здесь происходит сбой, вставляя маршрут в 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;
}
Мы нажимаем «удалить старое» и переходим к ошибкам в dtor для одного из QDateTime
х (на самом деле это личное QTimeZone
членом).
мой ezspSendUnicast()
Метод может нормально работать в течение сотен тысяч итераций перед сбоем. Я не думаю, что я теряю память (согласно Вальгринду). Я считаю, что объект Route, который я передаю в ezspSendUnicast (), должным образом защищен мьютексом, но, возможно, я что-то пропустил (но я думал, что QSharedData
в любом случае был поточно-ориентированным для копирования при записи).
Любая идея о том, как решить эту проблему, будет принята с благодарностью!
Случаи QSharedData
при доступе через отдельный экземпляр из QSharedDataPointer
, на самом деле, можно получить доступ из нескольких потоков одновременно, и это безопасно. Указатель общих данных будет атомарно принимать копию ссылочных данных по мере необходимости.
Так что, пока вы работаете самостоятельно пример из Route
Объект, все в порядке. Что вы не можете сделать, это использовать ссылка к элементу, удерживаемому контейнером. Вы можете использовать const-ссылку на временный объект, созданный из того, что содержится в контейнере, то есть в новый экземпляр.
В соответствии с документация:
Правильная блокировка должна использоваться, когда делиться экземпляром из
неявно разделяемый класс между потоками.
это никогда не исправить чтобы получить доступ к ссылке на общий экземпляр неявно общего класса без удержания блокировки.
Вы должны всегда есть новый экземпляр. Вы Можно copy создает временный экземпляр, а затем передает его через константную ссылку, например, в качестве аргумента в вызове функции. Такие const-links-to-временные экземпляры задерживают уничтожение ссылочного временного объекта, пока они больше не нужны.
Это относится ко всем неявно разделяемым классам в 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()) ...
Но это правильно, поскольку это постоянная ссылка на временный экземпляр, срок жизни которого по мере необходимости увеличивается. Итак, вы ссылаетесь на новый экземпляр, к которому никто кроме вас не имеет доступа.
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 для временного), не удерживая мьютекс.
Также возможно, что у вас есть какая-то другая ошибка памяти или многопоточности, которая проявляется в несвязанном месте.
Других решений пока нет …