Qthread多线程未预期结果

Qthread multithread unexpect result

本文关键字:结果 多线程 Qthread      更新时间:2023-10-16

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

  1. 两个线程,同时向控制台打印"foo"answers"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

控制器.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有困难的人。这是由在这些问题上帮助我的人贡献的。主线程无法从工作线程、多线程中的qt代码序列和当前帖子中获得信号。此外,这篇文章读起来很不错。

首先,您需要创建一个继承QObject的Object。该对象是运行多线程作业的位置。正如文章中提到的,对象构造函数不应该包含使用new在堆中创建的任何内容。构造函数对象最好尽可能简单,因为作业不应该在构造函数中完成。相反,所有作业都应该放在此对象的槽中。

CCD_ 5信号的产生也是非常重要的。这个信号应该在每个作业函数结束时发出,以便告诉线程控制器它已经完成了作业。这也是一个很好的方法,可以将你的工作结果放在像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);
};

首先,我们创建这个Object和一个QThread Object。在堆或堆栈中创建QThread对象时要小心。这将导致两种不同的方法来结束线程。

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

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

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

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

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

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

eventloop.exec();

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

threadStack.wait();

错误太多。。。

除了之前Antwane的回答之外,几乎没有其他评论了。

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

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

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

参见QThread详细说明中的Controler示例。CCD_ 39可以在其他偶数循环(而不是它自己的)中被删除。然而,即使当主偶循环(主线程)捕捉到线程QThread::finished()信号时,QThread仍然可能繁忙。为了确保线程在删除之前真正完成,函数QThread::wait()

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

一些提示:

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

从您的例子来看,您(至少)有两个解决方案来改进实现:

  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开始执行

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

祝你好运;)