QT多线程之QThread

2022年4月1日 221点热度 1人点赞 0条评论

QThread 是Qt提供的一个线程类,要使用它实现多线程编程有两种方法,一种是新建一个QThread对象,然后使用QObject::moveToThread(QThread*),将一个QObject对象的事件循环转移到新的线程中。另一种是继承QThread类然后重新实现run()方法,run()将在调用QThread::start()的时候在新的线程中被调用。

 QObject::moveToThread(QThread *targetThread)

这个函数可以将对象以及子对象的线程关联移动到targerThread中,但是这个对象不能有父对象,否则会失败。

如果targetThread 为nullptr,那么对象将不与任何线程关联,事件循环将不在执行,也就是说会接收不到任何事件,当然也无法响应信号。

在线程关联移动的过程中,会重置对象的定时器到0,如果一直频繁的切换线程关联,那么理论上可以达到定时器延时的效果。

移动之后,会在新的线程里开启事件循环,从官方文档中得知,本质上是在QThread::run()中调用了exec()函数,exec()函数的作用就是执行事件循环,直到对象被调用exit()在退出。

public slots:
    void doWork(const QString& parameter) {
        QString result;
        std::cerr << "do work threadID: " << QThread::currentThreadId()  
            <<",parameter :" << parameter.toStdString() << std::endl;
        emit resultReady(result);
    }

signals:
    void resultReady(const QString& result);
};

class Controller : public QObject
{
    Q_OBJECT
        QThread workerThread;
public:
    Controller() {
        Worker* worker = new Worker;
        worker->moveToThread(&workerThread);
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
        connect(this, &Controller::operate, worker, &Worker::doWork);
        connect(worker, &Worker::resultReady, this, &Controller::handleResults);
        workerThread.start();
    }
    ~Controller() {
        workerThread.quit();
        workerThread.wait();
    }
public slots:
    void handleResults(const QString&) {};
signals:
    void operate(const QString&);
};

然后再main函数中调用

std::cerr << "main thread ID: " << QThread::currentThreadId() << std::endl;
Controller controller;
controller.operate("test");

运行结果:

main thread ID: 0000000000006710
do work threadID: 000000000000391C,parameter :test

可以看到当调用信号时,绑定的槽函数是在新的线程中被执行的。

需要注意的是如果在连接信号与槽的时候在第五个参数传入Qt::DirectConnection,因为这样设置之后,槽函数不经过事件循环,会被立即调用,所以这个槽函数会被当前线程调用,而不是关联事件循环的线程。

注意

对象切换线程关联时,并不能再任意线程调用MoveToThread(),必须在对象当前关联的线程中调用。有一种情况可以例外,那就是这个对象没有跟任何线程进行关联的情况下。

继承QThread

这个比较简单,只需在继承之后重写run()方法即可。

class MyThread :public QThread {
public:
    MyThread();
    ~MyThread();
protected:
    void run() override {
        std::cerr << "MyThread threadID: " << QThread::currentThreadId() << std::endl;
    };
};

然后再main()函数中调用

MyThread t;
t.start(); //启动
t.exit(); //退出
t.wait(); //等待线程退出

这样就可以实现多线程。

关于线程退出

上述两种方法退出线程都是调用exit() 通知线程退出,然后调用wait()等待线程退出完成,最后再释放资源。

需要注意的一点是,exit()只是将线程退出这一信息放入事件循环,如果线程被阻塞没有进入事件循环,那么线程退出这一操作将永远不会执行,wait()也会一直阻塞到超时。比如你在槽函数或者run()中写了个while(1);,那么这个线程可能永远都不会退出了。

虽然可以调用terminate()强制释放资源,但是这个函数极其不稳定,调用之后什么不一定会立即停止线程,这取决于系统的调度机制,并且在结束线程时,可能不会正常的释放资源,很多时候会出现一些无法预料的问题,比如在访问上锁资源时,刚刚好在mutex.lock()之后线程被强制停止了,那么这个mutex很有可能陷入死锁的BUG,所以不推荐直接调用terminate()。

如果非要在线程中执行循环,那么最好的方法是在类中加入一个flag,然后while(flag),这样在需要退出线程时,将flag设置为false,然后再调用exit()就可以了

class MyThread :public QThread {
public:
    MyThread();
    ~MyThread();

    void setExitFlag(const bool& b) {
        mutex.lock();
        flag = b;
        mutex.unlock();
    }
    bool getExitFlag() {
        mutex.lock();
        bool b = flag;
        mutex.unlock();
        return b;
    }
private:
    bool flag = false;
    std::mutex mutex;
protected:
    void run() override {
        while (getExitFlag())
        {
            //干点啥?
        }
    };
};

 

大脸猫

这个人虽然很勤快,但什么也没有留下!

文章评论