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