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()) { //干点啥? } }; };
文章评论