Qt有一个术语Thread Affinity:
QObject的实例具有线程亲和力,意思是该object接收到queue signal和posted event会运行在该object所在线程。
将对象移动到新线程:
void QObject::moveToThread(QThread* targetThread)
作用:
- 改变object和它的children的thread affinity
- 如果object对象有parent,则不能移动,导致该object的槽函数还是运行在原来的线程,而不是tragetThread。
运行崩溃:QThread: Destroyed while thread is still running
该错误的原因是QThread析构的时候线程未退出。类似于忘记调用pthread_join(或pthread_detach)就退出线程了。
可以发现用户线程函数执行完了并不会退出线程,因为还有QThread的事件循环未退出,所以在退出线程的时候要调用以下代码:
thread->quit();
thread->wait();
quit():告诉线程退出事件循环。
wait(): 等待线程退出,相当于pthread_join。
finshed()信号:当线程执行完后会触发quit(),事件循环已经结束。
如果没有quit()就调用wait(),线程仍然处于事件循环中,则调用者一直阻塞在wait()。
只要调用了quit(),就会触发finished()信号,即使不调用wait()。
所以调用quit()退出事件循环是关键。
注意,这里说的事件循环是指QThread的事件循环,而不是用户的while循环等。
所以我们得先退出自己的while之类的循环再调用quit()退出QThread的事件循环,然后线程才算结束。
QThread和moveToThread的对象的析构顺序:
1. 对于没有用户定义的while之类的循环的QThread,我们可以这样析构:
事先调用了:connect(m_receiverThread, SIGNAL(finished()), m_receiver, SLOT(deleteLater()));
m_receiverThread->quit();
m_receiverThread->wait();
当finished()信号发出的时候会调用m_receiver的析构函数。
2. 对于有用户定义的while之类的循环的QThread,我们可以这样析构:
delete m_dataSender;
m_senderThread->quit();
m_senderThread->wait();
delete触发m_dataSender的析构函数,
在析构函数中我们可以修改条件变量,使线程退出用户的while循环,然后用户的线程函数就退出了。
注意这时候,QThread的事件循环还没退出,所以继续调用quit()和wait()。
发现我上面的用法并不好。
moveToThread官方推荐用法:官网用法
class Worker : public QObject {
Q_OBJECT
public:
Worker();
~Worker();
public slots:
void process();
signals:
void finished();
void error(QString err);
private:
// add your variables here
};
QThread* thread = new QThread;
Worker* worker = new Worker();
worker->moveToThread(thread);
connect(worker, SIGNAL (error(QString)), this, SLOT (errorString(QString)));
connect(thread, SIGNAL (started()), worker, SLOT (process()));
connect(worker, SIGNAL (finished()), thread, SLOT (quit()));
connect(worker, SIGNAL (finished()), worker, SLOT (deleteLater()));
connect(thread, SIGNAL (finished()), thread, SLOT (deleteLater()));
thread->start();
- 线程start触发wroker::process执行
- worker退出前发出一个finished信号,触发worker对象和线程的析构。
- QThread的finished信号触发QThread析构
官方推荐的用法是有局限性的,例如worker是响应式的或者需要标志位才能退出,这时候我们要通过外部的信号来触发worker的退出。