简述
要实现一个线程很简单,写一个函数,绑定一些数据,如果有必要的话,可以使用 mutex 或者其他方法来保证和线程的安全交互。
无论是 Win32、POSIX 或其他线程,工作原理都基本相同,并相当可靠。至少我敢说比 socket 更容易使用和处理。
worker-object
使用 QThread 时,最主要的事情是把它当成一个线程对象的封装。此封装提供了信号、槽和方法,可以轻松地使用 Qt 项目中的线程对象。这说明了子类化 QThread 并实现其 run() 函数是非常错误的。
一个 QThread 应该更像一个普通线程实例:准备一个 QObject 类和所有想要的功能,然后创建一个新的QThread,使用 moveToThread(QThread *) 将 QObject 对象移动至线程中,并调用 QThread 实例的 start() 函数。就这样,再设置适当的信号/槽连接使它正常退出,所有的事情就都做完了。
实现一个简单的 Worker 类:
class Worker : public QObject {
Q_OBJECT
public:
Worker();
~Worker();
public slots:
void process();
signals:
void finished();
void error(QString err);
private:
// 在此处添加变量
};
至少需要添加一个共有的槽函数,用于触发实例,一旦线程开启,就立刻处理数据。
现在,看看这个类的基本实现:
Worker::Worker() {
// 这里,可以从构造函数参数拷贝数据到内部变量
}
Worker::~Worker() {
// 释放资源
}
// 开始处理数据
void Worker::process() {
// 这里,使用新分配资源
qDebug("Hello World!");
emit finished();
}
虽然 Worker 类没有做什么特别的事情,但它包含了所有必需的元素。当其主函数(这种情况下,是 process())被调用,就立即开始处理数据。当处理完成以后,会发射 finished() 信号,将被用来触发 QThread 实例的退出。
顺便说一句,这里需要注意的一个极为重要的事情 - 永远不应该在 QObject 类的构造函数中使用堆对象(new)。因为分配之后,会在主线程上执行,而非 QThread 实例。这意味着新创建的对象由主线程所拥有,而非 QThread 实例,这将使你的代码无法正常工作。相反,在这种情况下,在主函数(process())中分配这些资源,当被调用时,对象将会在新线程实例中。因此,它将拥有资源。
现在,来看看如何通过创建一个新的 Worker 实例,并把它放在一个 QThread 实例中:
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();
注意: connect() 系列是最为关键的部分。
当 worker 实例发出 finished() 信号时,线程就会退出。然后,使用相同的信号用于删除 worker。
为了防止讨厌的 crash(有可能当 thread 被删除时,还没有完全关闭),连接 thread(非 worker)的 finished() 信号到其自身的 deleteLater() 槽函数。这时,只有线程在完全退出时,才会被删除。