【问题标题】:Qthread multithread unexpect resultQthread多线程意外结果
【发布时间】:2015-11-15 10:21:41
【问题描述】:

我想用我的代码实现几件事:

  1. 两个线程,同时向控制台打印“foo”和“bar”100 次。顺序无关紧要。

  2. 创建线程的对象在完成时需要获得一个信号。在现实生活中,这个信号会携带线程的结果,创建者需要等待这个结果。

这是我为此示例编写的代码:

main.cpp

#include <QCoreApplication>
#include "controller.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Controller controller;
    return a.exec();
}

控制器.h

#ifndef CONTROLLER_H
#define CONTROLLER_H

#include <QObject>
#include <QThread>
#include <QEventLoop>
#include <QTimer>

#include "textwriter.h"

class Controller: public QObject
{
    Q_OBJECT
public:
    Controller(QObject* parent = 0);

signals:
    void startWrite();

public slots:
    void catchFinish();
};

#endif // CONTROLLER_H

controller.cpp

#include "controller.h"

Controller::Controller(QObject* parent):QObject(parent)
{
    // QThread
    QThread threadA;
    QThread threadB;
    // Worker that will send to the thread
    textWriter workerA("foo");
    textWriter workerB("bar");

    // Send the worker to the thread
    workerA.moveToThread(&threadA);
    workerB.moveToThread(&threadB);

    // I will emit startWrite() later to begin all the job
    connect(this, &Controller::startWrite, &workerA, &textWriter::write);
    connect(this, &Controller::startWrite, &workerB, &textWriter::write);

    // The worket will send back finish signal to me
    connect(&workerA, &textWriter::finish, this, &Controller::catchFinish);
    connect(&workerB, &textWriter::finish, this, &Controller::catchFinish);
    // Also kill the thread as well
    connect(&workerA, &textWriter::finish, &threadA, &QThread::quit);
    connect(&workerB, &textWriter::finish, &threadB, &QThread::quit);

    // If the thread was killed, let the system delete it
    connect(&threadA, &QThread::finished, &threadA, &QThread::deleteLater);
    connect(&threadB, &QThread::finished, &threadB, &QThread::deleteLater);


    // I start the thread here
    emit startWrite();

    QEventLoop eventloopA;
    connect(&workerA, &textWriter::finish, &eventloopA, &QEventLoop::quit);
    QEventLoop eventloopB;
    connect(&workerB, &textWriter::finish, &eventloopB, &QEventLoop::quit);

    threadA.start();
    threadB.start();
    eventloopA.exec();
    eventloopB.exec();
}

void Controller::catchFinish()
{
    qDebug() << "finish signal catched";
}

textwriter.h

#ifndef TEXTWRITER_H
#define TEXTWRITER_H

#include <QObject>
#include <QThread>
#include <QDebug>

class textWriter : public QObject
{
    Q_OBJECT
    QString text;
public:
    textWriter(QString text, QObject *parent = 0);
public slots:
    void write();

signals:
    void finish();
};

#endif // TEXTWRITER_H

textwriter.cpp

#include "textwriter.h"

textWriter::textWriter(QString text,QObject* parent):QObject(parent)
{
    this->text = text;
}

void textWriter::write()
{
    for (int i = 0; i < 100; ++i)
    {
        qDebug() << "Thread Number:\t"
                 << QThread::currentThreadId()
                 << "\tCount: "
                 << i
                 << "\t"
                 << text;
    }
    emit finish();
    qDebug() << "finish() signal emitted";
}

但是我得到了所有类型的结果。 我想知道这些结果的原因。

  1. 两个线程都按预期工作。他们依次做事并完成。
    理由:没有理由。这是所需的操作。

  2. 一个线程首先从 0 转到 99,然后第二个线程开始。程序完成。
    原因:由cpu调度器决定。

  3. 两个线程同时启动。他们做他们的工作。但是,当第一个线程完成时,程序终止。第二个线程的最后一次打印仍然是 count = 38。
    原因:?

  4. 两个线程都各司其职。但是控制台中有一个Error: double free or corruption
    原因:?

  5. QThread 在打印运行时销毁。
    原因: ?似乎与问题 3 的原因相同。

最新更新和建议:

我在这里发表一些评论,以帮助像我一样难以理解QThread 的人。这是由在这些问题main-thread-cannot-get-signal-form-worker-threadqt-code-sequence-in-multitread 和当前帖子中帮助我的人贡献的。另外,这个article 是一本非常好的读物。

首先你需要创建一个继承QObject的对象。这个对象是多线程作业运行的地方。如article 中所述,对象构造函数不应包含使用new 在堆中创建的任何内容。构造函数对象最好尽可能简单,因为作业不应该在构造函数中完成。相反,所有作业都应该放在这个对象的槽中。

创建finish() 信号也很重要。这个信号应该在每个作业函数结束时发出,以便告诉线程控制器它已经完成了它的工作。这也是将工作结果放入完成信号中的好方法,例如finish(int, QString,.......)

这是工作对象的样子:

class Object : public QObject
{
    Q_OBJECT
public:
    Object(QObject *parent = 0);

// All multi thread works should create in term of slot
public slots:
    void job1();
    void job2();
    void job3();
    void job4();

signals:
    void finish();
    void finish(int result);
};

首先,我们创建这个对象和一个QThread 对象。注意QThread 对象的创建方式,在堆中还是在堆栈中。它将导致两种不同的方法来结束线程。

QThread stackThread;
Object workerStack;
QThread* heapThread = new Qthread;
Object workerHeap;

创建线程和worker对象后,只需将worker移动到线程中即可。这里没有魔法。

workerStack.moveToThread(&stackThread);
workerHeap.moveToThread(heapThread);

然后连接一个信号,这样当QThread::start被调用时,对象就开始工作了:

connect(&stackThread, &QThread::started, &workerStack, &Object::job1);
connect(heapThread, &QThread::started, &workerHeap, &Object::job2);

您可以只使用finish() 信号来停止线程。

connect(&workerStack, &Object::finish, &stackThread, &QThread::quit);
connect(&workerHeap, &Object::finish, heapThread, &QThread::quit);

您可能需要多一个信号槽,以便在堆中创建的线程在完成后可以自行删除。这对于在堆栈中创建的线程来说是不需要的。

connect(&workerHeap, &Object::finish, heapThread, &QThread::deleteLater);

如果你的finish() 信号包含返回结果,你可以这样传达你的结果。

connect(&workerStack, &Object::finish, this, &THISSCOPE::catchFinish);

如果你需要等待线程结果继续你的程序,你可以使用QEventLoop。请不要使用不应执行此工作的 QThread::wait 函数。 wait 函数只是阻塞当前的事件循环,这样你就永远无法捕捉到finish 信号。

虽然使用QEventLoop可以让你接收到finish信号,但这并不是一个好的设计。有一些方法可以避免它,但我不会在这里详细说明。我只是用QEventLoop来演示当前示波器如何接收信号。代码如下:

QEventLoop eventloop;
connect(&workerStack, &Object::finish, &eventloop, &QEventLoop::quit);

现在,我们可以在所有 slot-slot 创建后启动线程:

threadStack.start();
threadHeap.start();

如果需要等待结果,则启动事件循环:

eventloop.exec();

当线程完成时,在堆中创建的线程应该发出finish 信号并删除自己(因为您连接到它的deleteLater 插槽)。但是,在堆栈上创建的线程有点棘手。如果您的主代码超出此处的范围,您可能会收到错误“QThread 在运行时被破坏”。这是因为主代码比QThread::quit 快一点。因此,您需要在此处输入QThread::waitwait 函数的真正用途是等待线程在作业完成后退出。 这不是等待工作完成。所以最后,我们添加这个:

threadStack.wait();

【问题讨论】:

    标签: c++ multithreading qt


    【解决方案1】:

    错误太多...

    除了之前的 Antwane 的回答之外,再多说几句。

    • 为分配在堆栈上的对象调用delete 是个坏主意。对象threadAthreadBController 构造函数的堆栈上。它们在构造函数完成时自动删除,并且由于deleteLater()而被第二次删除。

    • eventloopAeventloopB 正在运行时,主事件循环被阻塞。因此,Controller 对象在两个线程都未完成之前无法接收任何信号。不需要阻塞Controler 构造函数。我猜想阻止它的想法来自线程对象被破坏的问题。但是,可以使用new 创建线程对象来避免该问题。

    • 一般来说,通过finished 信号删除线程是不好的。一般来说,应用程序可能在调用QThread::quit() 之前和QThread::finished() 信号发出之前关闭。

    参见QThreadDetailed Description 中的Controler 示例。 QThread 可以在其他偶数循环中删除(不是它自己的)。但是,即使主偶数循环(主线程)捕获线程QThread::finished() 信号,QThread 仍然可能很忙。为了确保线程在删除之前真正完成,有函数QThread::wait()

    【讨论】:

    • “对分配在堆栈上的对象调用 delete 是个坏主意。”谢谢。我以错误的方式使用deleteLater
    • 在 eventloopA 和 eventloopA 运行时主事件循环被阻塞。我需要这个,因为在我的真实程序中,我需要等待线程的结果。事件循环可以实现这一点。但是看了其他文章后发现,像这样等待结果,同时阻塞构造函数,是一个很好的设计。
    • @tom 一定是奇怪的文章。如果您需要阻止Controler 构造函数,您可以使用threadA.wait(); threadB.wait(); 而不是创建新的eventloopAeventloopB。这样,构造函数将被阻塞,直到 threadAthreadB 完成。然后可以通过离开 ctor 块来安全地销毁线程。 QEventLoop 不能保证这一点,因为当收到信号 textWriter::finish 时线程仍然处于活动状态。在这种情况下,根本不需要deleteLater
    • @tom 如果你确实不想使用事件机制来处理worker完成状态(当主线程被阻塞时它不起作用)阻塞结果可以通过QFutureSynchronizerdoc.qt.io/qt-5/qfuturesynchronizer.html#details来实现没有很多QThread 实例。因此,可以使用QThread 解决问题而不阻塞任何事件循环,或者使用QFutureSynchronizer 进行阻塞。
    • @tom 这里的答案stackoverflow.com/questions/33716719/…的意思是需要在主线程中至少有一个事件循环(QCoreApplication已经有这样的循环了),但是这个事件循环不应该用用于阻塞操作。
    【解决方案2】:

    您描述的不同行为和错误很难调试。但是当线程的创建、销毁、开始和结束正确实现时,您可能会看到错误消失并且行为更加一致。

    一些提示:

    • 您不需要初始化 QEventLoop 实例。默认的 QThread 实现已经有一个 eventLoop(阅读 QThread::run()QThread::exec()
    • 启动线程的正确方法是调用QThread::start()。如果将QThread::started() 连接到Controller::startWrite() 并删除emit startWrite();,程序将执行相同的工作。

    从您的示例中,您有(至少)2 个改进实施的解决方案:

    1. 使用你的 textWriter 类作为它。在这种情况下,您可以创建一个 QThread 和一个 textWriter,使用 moveToThread 将 textWriter 实例移动到 QThread,将 QThread::started 连接到 textWriter::write 和 textWriter::finish 到 QThread::quit,最后使用 QThread 启动线程: :start()
    2. 另一种解决方案是修改textWriter。您可以将 textWriter 变成 QRunnable 的子代,并将 textWriter::write 重命名为 run()。然后使用 QThreadPool 开始执行。

    无论如何,我建议您阅读multithreading technologies in Qt 的摘要,这可能会帮助您找到与您的具体情况相匹配的解决方案!

    祝你好运;)

    【讨论】:

    • 我初始化 QEventLoop 因为我需要等待来自 textWriter 的信号。有没有其他方法可以暂停控制器并等待信号?
    • 我发现 connect(&amp;threadB, &amp;QThread::finished, &amp;threadB, &amp;QThread::deleteLater); 导致了 Error: double free or corruption 的不稳定
    • 我使用你的建议connect(&amp;threadA, &amp;QThread::started, &amp;workerA, &amp;textWriter::write);我不知道这些是否有帮助。
    • 应用了以上两个改动后,多线程运行相当稳定。至少它可以运行到最后,发出信号,控制器可以得到信号。
    • 但是QThread::Destroy while thread is running 仍然是个问题。它不会影响结果。它在线程完成后出现。只是想弄清楚原因............
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-26
    • 2011-08-05
    相关资源
    最近更新 更多