使用Qt编写程序时,为了避免耗时操作导致界面“假死”以及并行计算提高程序运行速度,我们需要使用多线程,下面记录四种多线程使用的方法。
1 QThread
第一种方法是使用 QThread 类,具体使用步骤如下:
- 需要创建一个线程类的子类,让其继承 QT 中的线程类 QThread,比如:
1 2 3 4
| class MyThread:public QThread { ...... }
|
- 重写父类的 run () 方法,在该函数内部编写子线程要处理的具体的业务流程,run 对于线程的作用相当于 main 函数对于应用程序。它是线程的入口,run 的开始和结束意味着线程的开始和结束。
1 2 3 4 5 6 7 8 9 10
| class MyThread:public QThread { ...... protected: void run() { ........ } }
|
- 在主线程中创建子线程对象,new 一个就可以了
1
| MyThread * subThread = new MyThread;
|
- 启动子线程,调用 start () 方法
当子线程别创建出来之后,父子线程之间的通信可以通过信号槽的方式,注意事项:
- 在 Qt 中在子线程中不要操作程序中的窗口类型对象,不允许,如果操作了程序就挂了
- 只有主线程才能操作程序中的窗口对象,默认的线程就是主线程,自己创建的就是子线程。
这种在程序中添加子线程的方式是非常简单的,但是也有弊端,假设要在一个子线程中处理多个任务,所有的处理逻辑都需要写到 run() 函数中,这样该函数中的处理逻辑就会变得非常混乱,不太容易维护。
2 moveToThread
第二种线程的创建方式弥补了第一种方式的缺点,用起来更加灵活,使用到的是moveToThread() 方法。
- 创建一个新的类,让这个类从 QObject 派生
1 2 3 4
| class MyWork:public QObject { ....... }
|
- 在这个类中添加一个公共的成员函数,函数体就是我们要子线程中执行的业务逻辑
1 2 3 4 5 6 7
| class MyWork:public QObject { public: ....... void working(); }
|
- 在主线程中创建一个 QThread 对象,这就是子线程的对象
1
| QThread* sub = new QThread;
|
- 在主线程中创建工作的类对象(千万不要指定给创建的对象指定父对象)
1 2
| MyWork* work = new MyWork(this); MyWork* work = new MyWork;
|
- 将 MyWork 对象移动到创建的子线程对象中,需要调用 QObject 类提供的 moveToThread() 方法
1 2 3 4
|
work->moveToThread(sub);
|
- 启动子线程,调用 start(), 这时候线程启动了,但是移动到线程中的对象并没有工作
- 调用 MyWork 类对象的工作函数,让这个函数开始执行,这时候是在移动到的那个子线程中运行的(包括这个对象的槽函数也会运行在子线程)
3 线程池 QRunnable + QThreadPool
线程池的作用是使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务,避免频繁创建线程降低效率。
Qt 中的 QThreadPool 类管理了一组 QThreads, 里边还维护了一个任务队列。QThreadPool 管理和回收各个 QThread 对象,以帮助减少使用线程的程序中的线程创建成本。每个Qt应用程序都有一个全局 QThreadPool 对象,可以通过调用 globalInstance() 来访问它。也可以单独创建一个 QThreadPool 对象使用。
在 Qt 中使用线程池需要先创建任务,添加到线程池中的每一个任务都需要是一个 QRunnable 类型,因此在程序中需要创建子类继承 QRunnable 这个类,然后重写 run() 方法,在这个函数中编写要在线程池中执行的任务,并将这个子类对象传递给线程池,这样任务就可以被线程池中的某个工作的线程处理掉了。
- 创建一个线程子类,让其继承 QRunnable和QObject(继承 QObject 可以使用信号和槽)。
1 2 3 4
| class MyWork :public QRunnable, public QObject { ....... }
|
- 重写父类的run()方法,在该函数内部编写子线程要处理的具体的业务流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class MyWork :public QRunnable, public QObject { Q_OBJECT public: explicit MyWork(); ~MyWork();
void run() override; }
MyWork::MyWork() { setAutoDelete(true); } void MyWork::run() { ...... }
|
- 在主线程中初始化线程池
1 2
| QThreadPool::globalInstance()->setMaxThreadCount(4);
|
- 在主线程实例化子线程对象,并将其放入线程池
1 2
| MyWork* task = new MyWork; QThreadPool::globalInstance()->start(task);
|
4 QtConcurrent
QtConcurrent::run能够方便快捷的将任务丢到线程池的子线程中去执行**,无需继承任何类,也不需要重写函数**,使用非常简单,但是执行完毕后没有任何信号,目前我想到的还是需要在子线程函数中添加信号,完成后触发信号表示子线程函数运行完毕。
- 新建一个子线程类(也可以不建),将需要处理的程序放到一个公共函数中,并设置相应信号
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct myData { int test = 2; };
class MyConcurrent : public QObject { Q_OBJECT public: myData work();
signals: void sig_finished(); };
|
1 2 3 4 5 6 7 8 9
| myData MyConcurrent::work() { qDebug() << "Concurrent子线程对象的地址: " << QThread::currentThread(); ...... emit sig_finished(); myData m; m.test = 1; return m; }
|
- 在主线程中实例化子线程类对象,将子线程函数完成的信号进行信号和槽连接。
1 2 3
| MyConcurrent* mythread3 = new MyConcurrent; connect(mythread3, SIGNAL(sig_finished()), this, SLOT(OnSigFinished()));
|
- 将其工作函数传入QtConcurrent::run()函数中,并使用QFuture接收函数返回值。
1 2 3 4 5 6 7 8 9
| template <typename T> QFuture<T> QtConcurrent::run(Function function, ...)
template <typename T> QFuture<T> QtConcurrent::run(QThreadPool *pool, Function function, ...)
template <typename T> QFuture<T> QtConcurrent::run(className *obejct, Function function, ...)
QFuture<myData> future = QtConcurrent::run(mythread3, &MyConcurrent::work);
|
- 在主线程对应的槽函数中接收子线程计算的结果。
1 2 3 4 5 6 7
| void MultiThread::OnSigFinished() { if (future.isFinished()) { myData&& m = future.result(); } }
|
5 比较
| **需要线程的生命周期 ** |
开发场景 |
解决方案 |
| 单次调用 |
在其他的线程中运行一个方法,当方法运行结束后退出线程。 |
(1)编写一个函数,然后利用 QtConcurrent::run()运行它;(2)从 QRunnable 派生一个类,并利用全局线程池QThreadPool::globalInstance()->start()来运行它。(3) 从QThread派生一个类, 重载QThread::run() 方法并使用QThread::start()来运行它。 |
| 单次调用 |
一个耗时的操作必须放到另一个线程中运行。在这期间,状态信息必须发送到GUI线程中。 |
使用 QThread,,重载run方法并根据情况发送信号。.使用queued信号/槽连接来连接信号与GUI线程的槽。 |
| 常驻 |
有一对象位于另一个线程中,将让其根据不同的请求执行不同的操作。这意味与工作者线程之间的通信是必须的。 |
从QObject 派生一个类并实现必要的槽和信号,将对象移到一个具有事件循环的线程中,并通过queued信号/槽连接与对象进行通信。 |
6 主要参考
大丙多线程
QtConcurrent::run()+QThreadPool
QT多线程开发
QT多线程之QtConcurrent::run()
7 实验代码
我的github