Ошибка QThread ASSERT в QMutexLocker: указатель QMutex смещен,

Я пытаюсь создать загрузчик, который будет создавать новые потоки, и в каждом потоке у меня есть QNetworkAccessManager. Все потоки загрузки имеют ссылку на общий список и будут разделять его, используя начальный и конечный индексы.

Загрузчик выглядит примерно так:

 class FileUploader : public QObject {
    Q_OBJECT

public:
    explicit FileUploader(QList<FileInfoWrapper> &fileList, const int start = 0, const int offset = 0, QObject *parent = 0);


    void uploadNext();

    QString containerName;

private:
    int start_, offset_, iterator_;
    QList<FileInfoWrapper> &fileList_;
    RestFileUploader *restFileUploader;

signals:
    void progressChangedAt(int row);
    void statusChangedAt(int row);
    void finished();

public slots:
    void init();

private slots:
    void setUploadProgress(qint64 tranfered);
    void handleRequestFinished(QNetworkReply* reply);
    void handleSslErros(QNetworkReply *reply, const QList<QSslError> &errors);
    void handleNetworkError(QNetworkReply::NetworkError error);

};

Затем в функции run() я создаю новый RestFileUploader(this) (почти объект, который создает свой собственный новый QNetworkAccessManager(this) и помещает в него запросы), так что в конструкторе ничего не создается (что может привести к оказаться в основном потоке вместо этого?). Функция запуска создает запрос, который будет передан QNetworkAccessManager, а затем ничего не делает, пока это не сигнализирует "завершено (QNetworkReply)", а затем я беру следующий (и так далее, пока список не будет пройден).

Затем я создаю два новых потока в основном приложении, и когда я запускаю их с помощью run(), он работает, за исключением того, что идентификатор одинаков для обоих потоков. Если я вместо этого вызову «start()», он вылетит с ошибкой: QObject: Невозможно создать дочерние элементы для родителя, который находится в другом потоке. (Родительский поток — FileUploader (0x2580748), родительский поток — QThread (0x4fb2b8), текущий поток — FileUploader (0x2580748)

НО! Непосредственно перед тем, как начать просмотр списка, я печатаю threadId, и они уже не совпадают.

Что я делаю не так или мне просто нужно сделать это: http://labs.qt.nokia.com/2006/12/04/threading-without-the-headache/ ?

Редактировать:

Я изменил его и переименовал run в start и сделал эту обертку (и я больше не вызываю NetworkAccessManager или RestFileUploader с помощью «этого»):

FileUploader *fileUploader = new FileUploader(fileList_, start, (offset == 0 ? (fileList_.count() - start) : offset));
QThread *fileUploaderThread = new QThread;
fileUploader->moveToThread(fileUploaderThread);

connect(fileUploader, SIGNAL(progressChangedAt(int)), model_, SLOT(reportProgressChanged(int)));
connect(fileUploader, SIGNAL(statusChangedAt(int)), model_, SLOT(reportStatusChanged(int)));

fileUploaderThread->start();
QMetaObject::invokeMethod(fileUploader, "init", Qt::QueuedConnection);

При загрузке одного объекта работает, так как тогда я использую только один поток. Но когда у меня появилось больше объектов, которые я разделил, приложение резко упало с этим сообщением об ошибке:

ASSERT failure in QMutexLocker: "QMutex pointer is misaligned", file ..\..\include/QtCore/../../../../../../ndk_buildrepos/qt-desktop/src/corelib/thread/qmutex.h, line 100
Invalid parameter passed to C runtime function.
Invalid parameter passed to C runtime function.

Пожалуйста помогите

Редактировать:

загрузчик файлов.cpp

#include "fileuploader.h"

FileUploader::FileUploader(QList<FileInfoWrapper> &fileList, const int start, const int offset, QObject *parent)
    : QObject(parent), start_(start), offset_(offset), iterator_(start - 1), fileList_(fileList) {
}

void FileUploader::init() {
    restFileUploader = new RestFileUploader();

    connect(restFileUploader, SIGNAL(uploadProgress(qint64)), this, SLOT(setUploadProgress(qint64)));
    connect(restFileUploader, SIGNAL(requestFinished(QNetworkReply*)), this, SLOT(handleRequestFinished(QNetworkReply*)));
    connect(restFileUploader, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), this, SLOT(handleSslErros(QNetworkReply*,QList<QSslError>)));
    connect(restFileUploader, SIGNAL(networkError(QNetworkReply::NetworkError)), this, SLOT(handleNetworkError(QNetworkReply::NetworkError)));

    containerName = "temp"

    qDebug() << "thread" << this->thread()->currentThreadId() << start_ << ":" << offset_;

    uploadNext();
}

void FileUploader::uploadNext() {
     qDebug() << "uploadNext" << this->thread()->currentThreadId();

    if((iterator_ + 1) < (start_ + offset_)) {
        iterator_++;

        restFileUploader->putBlob(containerName, fileList_.at(iterator_).fileName(), fileList_.at(iterator_).fileInfo().filePath());

    } else emit finished();
}

void FileUploader::setUploadProgress(qint64 tranfered) {

    fileList_[iterator_].setProgress(tranfered);

    emit progressChangedAt(iterator_);
}

void FileUploader::handleRequestFinished(QNetworkReply* reply) {

    qDebug() << "finished blob: " << iterator_ << " in thread " << this->thread()->currentThreadId();

    if(reply->error() > QNetworkReply::NoError) {
        qDebug() << reply->errorString();

        fileList_[iterator_].uploadFailed();

        emit progressChangedAt(iterator_);

    } else fileList_[iterator_].uploadFinished();

    emit statusChangedAt(iterator_);

    uploadNext();
}

void FileUploader::handleNetworkError(QNetworkReply::NetworkError error) {

    if(error > QNetworkReply::NoError) {
        fileList_[iterator_].uploadFailed();

        restFileUploader->cancelCurrentRequest();

        emit progressChangedAt(iterator_);
        emit statusChangedAt(iterator_);
    }
}

void FileUploader::handleSslErros(QNetworkReply *reply, const QList<QSslError> &errors) {

    if(reply->error() > QNetworkReply::NoError) {

        qDebug() << reply->errorString();

        fileList_[iterator_].uploadFailed();

        restFileUploader->cancelCurrentRequest();

        emit progressChangedAt(iterator_);
        emit statusChangedAt(iterator_);
    }
}

#include "restfileuploader.h"

void RestFileUploader::putBlob(const QString& container, const QString& blob, const QString& filePath) {
    QFile *uploadFile = new QFile(filePath, this); // <--- this maybe?
    uploadFile->open(QIODevice::ReadOnly); 

    QNetworkRequest request = this->createRestRequest("PUT", QString("%1/%2").arg(container, blob), uploadFile->size(), headers);

    reply_ = accessManager_->put(request, uploadFile);

    connect(reply_, SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(reportUploadProgress(qint64, qint64)));
    connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(reportNetworkError(QNetworkReply::NetworkError)));

    qDebug() << this->thread()->currentThreadId();
}

void RestFileUploader::cancelCurrentRequest() {
    reply_->abort();
}

RestFileUploader::~RestFileUploader() {
    qDebug() << "RestFileUploader deleted";

    reply_->deleteLater();
}

Итак... 1 тред с одной вещью для загрузки == ок. 2 объекта на двух потоках тоже в порядке. Когда я пытаюсь загрузить 3 или более объектов в два потока, все идет к чертям.

Кроме того, может ли это быть связано с тем, что пользовательский интерфейс считывает информацию о файлах одновременно с ее изменением?

РЕДАКТИРОВАТЬ: по какой-то причине мое приложение теперь работает в 4.8.0, когда я компилирую его в визуальной студии. Может это как-то связано с версией 4.7.4?


person chikuba    schedule 27.02.2012    source источник
comment
Конечно, вам не следует звонить run() напрямую; это то, что будет делать новый поток после start()ed. Да, я знаю, что это тоже не работает для вас (следовательно, это комментарий, а не ответ), но run() действительно не следует вызывать из основного потока.   -  person Donal Fellows    schedule 27.02.2012
comment
Я мог бы подумать, что это как-то связано с тем, что я не храню весь свой код в run(). Я мог бы просто завернуть его   -  person chikuba    schedule 27.02.2012
comment
Кроме того, может ли это быть связано с тем, что пользовательский интерфейс считывает информацию о файлах одновременно с ее изменением? Да, наверное. Как насчет использования потокобезопасных сигналов/слотов для отправки информации в основной поток (где происходят события пользовательского интерфейса)?   -  person tmpearce    schedule 27.02.2012
comment
Я пытался найти информацию об этом, но не могу найти. Любой хороший сайт, где я могу прочитать об этом?   -  person chikuba    schedule 27.02.2012
comment
Попробуйте: разработчик. qt.nokia.com/doc/qt-4.8/   -  person tmpearce    schedule 27.02.2012
comment
удалил чтение списка из графического интерфейса, и теперь я ничего не распечатываю. Я до сих пор не знаю, почему он случайно вылетает.   -  person chikuba    schedule 27.02.2012
comment
чикуба. У вас есть аналогичный вопрос, который вы задали сегодня, когда я предлагал вам использовать сигнал/слот для передачи вашей памяти: stackoverflow.com/questions/9476045/ . +1 @tmpearce за то же предложение   -  person jdi    schedule 28.02.2012


Ответы (3)


QThread::start() — это то, что фактически запускает thread (как другое thread). QThread::run() — это обычная функция, поэтому, если вы вызываете ее без предварительного вызова start(), вы выполняете ее в основном потоке.

Самое смешное, что ваш производный класс был создан в основном потоке, поэтому он «принадлежит» основному потоку. Я предполагаю, что вы отдаете свой класс в качестве родителя чему-то другому; это то, что генерирует сообщение об ошибке, когда вы пытаетесь сделать это после вызова start(). Можете ли вы оставить эти объекты без родителей?

Вы не сможете создавать объекты графического интерфейса в другом потоке, Qt просто не позволяет этого (пока?). Но другие объекты могут быть созданы, вы просто не можете дать им родителя в другом потоке.

person tmpearce    schedule 27.02.2012
comment
Когда я создаю загрузчик, я вообще не даю ему родителя. Какой из них будет qui объектом? НАМ? - person chikuba; 27.02.2012
comment
Вы сказали new AzureBlobStorageManager(this) - этот класс передает указатель на поток в NAM в качестве родителя? Боюсь, я не знаком со спецификой NAM. - person tmpearce; 27.02.2012
comment
я просто поленился и сократил NetworkAccessManager. AzureBlobStorageManager — это просто созданный мной класс, который содержит некоторые функции и NetworkAccessManager и обрабатывает вызовы REST. Так вы бы порекомендовали мне вообще не использовать это? - person chikuba; 27.02.2012
comment
Я не говорю, что вы не можете передать this, мне просто интересно, передается ли где-то this в качестве аргумента constructor(QObject* parent). Из документации я вижу, что QNetworkAccessManager принимает только один параметр (QObject* parent) в конструкторе, поэтому, поскольку вы сказали, что дали ему аргумент, я подозреваю, что именно в этом ваша проблема. - person tmpearce; 27.02.2012
comment
В своем редактировании вы говорите, что переименовали run() в start() - это, вероятно, не будет работать так, как вы хотите. QThread::start() вызывает run(), где run() является виртуальной функцией, поэтому вызывается ваша производная реализация. Если вы определите Derived::start() и вызовете его, QThread::start() на самом деле не будет вызываться, поэтому вы фактически не запустите поток. Вы хотите, чтобы ваши вещи находились в run(), просто не вызывайте их напрямую (т. е. вместо этого вызывайте start()). - person tmpearce; 27.02.2012

Ошибка, которую вы получаете

ASSERT failure in QMutexLocker: "QMutex pointer is misaligned"

возникает при создании объекта QMutexLocker, если объект QMutex, переданный конструктору, не выровнен по 2-байтовой границе в ОЗУ (по крайней мере, в Qt 4.7.1).

Объект QMutexLocker использует одну переменную-член для представления как местоположения мьютекса в памяти, так и его состояния (независимо от того, заблокирован он или нет). Состояние представляется младшим битом переменной, а младший бит указателя мьютекса считается равным нулю. Если этот бит не равен нулю, выдается исключение ASSERT, описанное выше.

Единственная причина смещения указателя QMutex, о которой я могу думать, - это утечка памяти или повреждение. Проверьте, уничтожаете ли вы всю память, которую вы выделяете (особенно в циклах), и проверяйте все приведения типов (вы могли привести указатель меньшего типа к указателю большего типа, повредив память) и строки с завершающим нулем (что повредит память). если не прекращено должным образом). Наконец, проверьте потокобезопасность всей памяти, которую вы разделяете между потоками.

person d11    schedule 14.06.2012
comment
Я получил это, потому что пытался заблокировать мьютекс в уже уничтоженном объекте. Таким образом, доступ к уже освобожденной памяти также может быть причиной этой ошибки. - person Ignitor; 23.01.2018

Вероятно, это связано с порядком создания некоторого объекта в вашем приложении. Я бы рекомендовал наследовать от QObject вместо QThread и вместо этого перемещать объект в рабочий поток (QObject::moveToThread()). Также переместите любой код, инициализированный в FileUploader, в отдельный слот в FileUploader, скажем, init(). Вызовите init (QMetaObject::invokeMethod()) во время работы потока (после вызова start в рабочем потоке). Например.:

FileUploader : public QOject
{
...
public slots:
void init() { Foo *foo = new Foo(this); }
}

// main thread
QThread worker;
FileUploader *fup = new FileUploader();
fup->moveToThread(&worker);
worker.start();
QMetaObject::invokeMethod(fup, "init", Qt::QueuedConnection); 
//
person cstream    schedule 27.02.2012
comment
Ага. забыл сказать, что я сделал эти изменения, кроме последнего с метаобъектом. однако можно ли создать поток в куче? пробовал таким образом, но они вышли за рамки и умерли, когда функция закончилась - person chikuba; 27.02.2012
comment
Сделал то, что вы сказали, и теперь он работает с большим количеством потоков, но я могу загрузить только один объект в поток. Когда захожу в uploadNext все вылетает - person chikuba; 27.02.2012
comment
да, это будет хорошо. просто убедитесь, что элементы, выделенные и являющиеся дочерними (в дереве объектов) FileUploader, созданы в слоте инициализации. - person cstream; 27.02.2012
comment
Тогда, вероятно, проблема с синхронизацией, нужно будет увидеть больше кода и, например, трассировку стека, чтобы сказать наверняка. - person cstream; 27.02.2012
comment
сделайте uploadNext слотом и назовите его так же, как в моем примере вызывается init. - person cstream; 27.02.2012
comment
но его предполагается рекурсивным. когда будет испущен сигнал окончания, будет вызвана функция uploadNext. - person chikuba; 27.02.2012
comment
1. если RestFileUploader является QObject, вы можете установить родителя в этом слоте инициализации. 2. убедитесь, что вы не вызываете init более одного раза для каждого создаваемого вами объекта. 3. Вы создаете новые темы каждый раз, когда заканчивается одна? - person cstream; 27.02.2012
comment
Добавляете ли вы материал в fileList из исходного потока после того, как рабочий поток будет запущен? Это не было бы потокобезопасным. Как рекомендует cstream, вы можете создать слот для добавления файлов в список и использовать потокобезопасные сигналы для отправки указателей через границу потока. - person tmpearce; 27.02.2012
comment
1. сделано. 2. Только один на поток. 3. Создает 2 потока в начале и распределяет данные по ним. Если у меня есть только один поток, он работает отлично. Когда я запускаю 2+ потока с 3+ объектами, происходит сбой - person chikuba; 27.02.2012
comment
Ничего не добавляется в список файлов во время выполнения. Единственное, что происходит, это то, что fileList.at(i).progress и статус будут изменены по мере их загрузки. - person chikuba; 27.02.2012
comment
Во-первых, похоже, что ваша система start/offset подвержена переполнению конца списка, если вы не будете осторожны. Во-вторых, с чем связано finished() — сигнализирует ли один поток о том, что это сделано, и каким-то образом портит fileList? - person tmpearce; 27.02.2012
comment
Я больше ничего не вижу. Вам нужно будет запустить это в отладчике и посмотреть, откуда исходит вызов, вызывающий утверждение. Вероятно, неверный указатель..., когда у вас есть обзор трассировки вызовов, убедитесь, что путь является потокобезопасным. Кроме того, убедитесь, что доступ к списку файлов осуществляется потокобезопасным способом, например, путем разделения списка, и пусть FileUploaders владеет частью списка файлов вместо ссылки на весь список (блокировка не требуется) . - person cstream; 27.02.2012
comment
Как я могу отправить ссылку на часть списка? Он должен указывать на этот список, потому что он связан с представлением модели. Или я должен просто передать кусок (не ссылку) в поток, а затем из основного потока вызвать модель и внести изменения через нее? Я серьезно понятия не имею, как решить эту проблему наилучшим образом, и она продолжает падать... - person chikuba; 28.02.2012
comment
Вы не можете отправить ссылку на части списка, мое предложение состояло в том, чтобы разделить список и передать его потоку, когда поток закончит с ним, снова объединить подсписок вместе. Судя по вашему последнему комментарию, это может быть не лучшим решением. Лучшим решением было бы отправить сигнал из потока обратно в основной поток и обновить там список файлов. Например, сделайте это в основном потоке fileList_[iterator_].uploadFailed(); и пусть класс FileUploader содержит константную ссылку. в список файлов (чтобы избежать изменения списка в потоках). - person cstream; 28.02.2012
comment
Кстати, с этой реализацией я действительно могу передать это как родителя, так как это создается в другом потоке. Я знаю, что это относится к объектам, унаследованным от QThread, но как насчет перемещения объектов QOB в поток? - person chikuba; 29.02.2012
comment
В моем примере это было бы нормально (функция инициализации), так как она будет вызываться в цикле событий потоков. QObject, который перемещается в другой поток, не должен иметь родителя, QObject и его дочерние элементы изменят привязку к потоку (кстати, извините за поздний ответ) - person cstream; 16.03.2012