Qt多線程的實作方式有:
1. 繼承QThread類,重寫run()方法
2. 使用moveToThread将一個繼承QObject的子類移至線程,内部槽函數均線上程中執行
3. 使用QThreadPool,搭配QRunnable(線程池)
4. 使用QtConcurrent(線程池)
為什麼要用線程池?
建立和銷毀線程需要和OS互動,少量線程影響不大,但是線程數量太大,勢必會影響性能,使用線程池可以減少這種開銷。
一、繼承QThread類,重寫run()方法
缺點:
1. 每次建立一個線程都需要繼承QThread,實作一個新類,使用不太友善。
2. 要自己進行資源管理,線程釋放和删除。并且頻繁的建立和釋放會帶來比較大的記憶體開銷。
适用場景:QThread适用于那些常駐記憶體的任務。
1 //mythread.h
2 #ifndef MYTHREAD_H
3 #define MYTHREAD_H
4
5 #include <QThread>
6
7 class MyThread : public QThread
8 {
9 public:
10 MyThread();
11 void stop();
12
13 protected:
14 void run();
15
16 private:
17 volatile bool stopped;
18 };
19
20 #endif // MYTHREAD_H
1 //mythread.cpp
2 #include "mythread.h"
3 #include <QDebug>
4 #include <QString>
5
6 MyThread::MyThread()
7 {
8 stopped = false;
9 }
10
11
12
13 void MyThread::stop()
14 {
15 stopped = true;
16 }
17
18
19
20 void MyThread::run()
21 {
22 qreal i = 0;
23
24 while( !stopped )
25 {
26 qDebug() << QString("in MyThread: %1").arg(i);
27 sleep(1);
28 i++;
29 }
30 stopped = false;
31 }
1 //widget.h
2 #ifndef WIDGET_H
3 #define WIDGET_H
4
5 #include <QWidget>
6 #include "mythread.h"
7
8
9 QT_BEGIN_NAMESPACE
10 namespace Ui { class Widget; }
11 QT_END_NAMESPACE
12
13 class Widget : public QWidget
14 {
15 Q_OBJECT
16
17 public:
18 Widget(QWidget *parent = nullptr);
19 ~Widget();
20
21 private slots:
22 void on_startBut_clicked();
23
24 void on_stopBut_clicked();
25
26 private:
27 Ui::Widget *ui;
28 MyThread thread;
29 };
30 #endif // WIDGET_H
1 //widget.cpp
2
3 #include "widget.h"
4 #include "ui_widget.h"
5
6 Widget::Widget(QWidget *parent)
7 : QWidget(parent)
8 , ui(new Ui::Widget)
9 {
10 ui->setupUi(this);
11 ui->startBut->setEnabled(true);
12 ui->stopBut->setEnabled(false);
13 }
14
15 Widget::~Widget()
16 {
17 delete ui;
18 }
19
20
21 void Widget::on_startBut_clicked()
22 {
23 thread.start();
24 ui->startBut->setEnabled(false);
25 ui->stopBut->setEnabled(true);
26 }
27
28 void Widget::on_stopBut_clicked()
29 {
30 if( thread.isRunning() )
31 {
32 thread.stop();
33 ui->startBut->setEnabled(true);
34 ui->stopBut->setEnabled(false);
35 }
36 }
二、使用moveToThread将一個繼承QObject的子類移至線程
更加靈活,不需要繼承QThread,不需要重寫run方法,适用于複雜業務的實作。
注意,該業務類的不同槽函數均在同一個線程中執行。
1 //worker.h
2 #ifndef WORKER_H
3 #define WORKER_H
4
5 #include <QObject>
6
7 class Worker : public QObject
8 {
9 Q_OBJECT
10
11 public:
12 Worker();
13
14 ~Worker();
15
16 public slots:
17 void doWork();
18
19 void another();
20
21 signals:
22 void stopWork();
23
24 };
25
26 #endif // WORKER_H
1 //worker.cpp
2 #include "worker.h"
3 #include <QDebug>
4 #include <QThread>
5
6 Worker::Worker()
7 {
8
9 }
10
11
12 Worker::~Worker()
13 {
14
15 }
16
17
18 void Worker::doWork()
19 {
20 qDebug() << "current thread id is " << QThread::currentThreadId();
21 emit stopWork();
22 }
23
24
25 void Worker::another()
26 {
27 qDebug() << "another current thread id is " << QThread::currentThreadId();
28 //emit stopWork();
29 }
1 //dialog.h
2 #ifndef DIALOG_H
3 #define DIALOG_H
4
5 #include <QDialog>
6 #include <QThread>
7 #include "worker.h"
8
9 QT_BEGIN_NAMESPACE
10 namespace Ui { class Dialog; }
11 QT_END_NAMESPACE
12
13 class Dialog : public QDialog
14 {
15 Q_OBJECT
16
17 public:
18 Dialog(QWidget *parent = nullptr);
19 ~Dialog();
20
21 signals:
22 void startWork();
23 void startAnother();
24
25 public slots:
26 void endThread();
27
28 private:
29 Ui::Dialog *ui;
30 QThread *m_pThread;
31 Worker *m_pWorker;
32 };
33 #endif // DIALOG_H
1 //dialog.cpp
2 #include "dialog.h"
3 #include "ui_dialog.h"
4 #include <QDebug>
5
6 Dialog::Dialog(QWidget *parent)
7 : QDialog(parent)
8 , ui(new Ui::Dialog)
9 {
10 ui->setupUi(this);
11
12 m_pThread = new QThread();
13 m_pWorker = new Worker();
14
15 connect(this, &Dialog::startWork, m_pWorker, &Worker::doWork);
16 connect(this, &Dialog::startAnother, m_pWorker, &Worker::another);
17 connect(m_pWorker, &Worker::stopWork, this, &Dialog::endThread);
18 m_pWorker->moveToThread(m_pThread);
19 m_pThread->start();
20 emit startWork();
21 emit startAnother();
22 }
23
24 Dialog::~Dialog()
25 {
26 delete ui;
27 delete m_pThread;
28 delete m_pWorker;
29 }
30
31
32 void Dialog::endThread()
33 {
34 qDebug() << "endThread";
35 m_pThread->quit();
36 m_pThread->wait();
37 }
不過我為什麼要用界面類呢?搞不懂!
三、使用QThreadPool,搭配QRunnable
QRunnable常用接口:
bool QRunnable::autoDelete() const;
void QRunnable::setAutoDelete(bool autoDelete);
- QRunnable 常用函數不多,主要設定其傳到底給線程池後,是否需要自動析構;
- 若該值為false,則需要程式員手動析構,要注意記憶體洩漏;
QThreadPool常用接口:
void QThreadPool::start(QRunnable * runnable, int priority = 0);
bool QThreadPool::tryStart(QRunnable * runnable);
- start() 預定一個線程用于執行QRunnable接口,當預定的線程數量超出線程池的最大線程數後,QRunnable接口将會進入隊列,等有空閑線程後,再執行;
- priority指定優先級
- tryStart() 和 start() 的不同之處在于,當沒有空閑線程後,不進入隊列,傳回false
業務類需要繼承QRunnable,并且重寫run()方法。注意,QRunnbale不是QObject的子類,可以發射信号,但用不了槽函數。
優點:無需手動釋放資源,QThreadPool啟動線程執行完成後會自動釋放。
缺點:不能使用信号槽與外界通信。
适用場景:QRunnable适用于線程任務量比較大,需要頻繁建立線程。QRunnable能有效減少記憶體開銷
1 //myrunnable.h
2 #ifndef MYRUNNABLE_H
3 #define MYRUNNABLE_H
4
5 #include <QRunnable>
6 #include <QString>
7
8
9 class MyRunnable : public QRunnable
10 {
11 public:
12 MyRunnable(const QString szThreadName);
13 void run();
14
15 private:
16 QString m_szThreadName;
17 };
18
19 #endif // MYRUNNABLE_H
1 //myrunnable.cpp
2 #include "myrunnable.h"
3 #include <QDebug>
4 #include <QThread>
5
6 MyRunnable::MyRunnable(const QString szThreadName) : m_szThreadName(szThreadName)
7 {
8
9 }
10
11
12 void MyRunnable::run()
13 {
14 qDebug() << "Start thread id : " << QThread::currentThreadId();
15 int iCount = 0;
16
17 while (1)
18 {
19 if(iCount >= 10)
20 {
21 break;
22 }
23
24 qDebug() << m_szThreadName << " count : " << iCount++;
25 QThread::msleep(500);
26 }
27 }
1 //mian.cpp
2 #include <QCoreApplication>
3 #include "myrunnable.h"
4 #include <QThreadPool>
5
6 static QThreadPool* g_pThreadPool = NULL;
7
8 int main(int argc, char *argv[])
9 {
10 QCoreApplication a(argc, argv);
11
12 MyRunnable* pRunnable1 = new MyRunnable("1# thread");
13 pRunnable1->setAutoDelete(true);
14
15 MyRunnable* pRunnable2 = new MyRunnable("2# thread");
16 pRunnable2->setAutoDelete(true);
17
18 g_pThreadPool = QThreadPool::globalInstance();
19
20 g_pThreadPool->start(pRunnable1);
21 g_pThreadPool->start(pRunnable2);
22
23 g_pThreadPool = NULL;
24
25 return a.exec();
26 }
四、使用QtConcurrent
Concurrent是并發的意思,QtConcurrent是一個命名空間,提供了一些進階的 API,使得在編寫多線程的時候,無需使用低級線程原語,如讀寫鎖,等待條件或信号。使用QtConcurrent編寫的程式會根據可用的處理器核心數自動調整使用的線程數。這意味着今後編寫的應用程式将在未來部署在多核系統上時繼續擴充。
QtConcurrent::run能夠友善快捷的将任務丢到子線程中去執行,無需繼承任何類,也不需要重寫函數,使用非常簡單。
QtConcurrent常用接口:
QFuture<T> QtConcurrent::run(Function function, ...)
QFuture<T> QtConcurrent::run(QThreadPool *pool, Function function, ...)
需要在pro檔案中添加:
QT += concurrent
1 //main.cpp
2 #include <QCoreApplication>
3 #include <QThread>
4 #include <QThreadPool>
5 #include <QtConcurrent/QtConcurrent>
6 #include <QDebug>
7 #include <QFuture>
8
9 static QThreadPool* g_pThreadPool = QThreadPool::globalInstance();
10
11 class HELLO
12 {
13 public:
14 QString hello(QString szName)
15 {
16 qDebug() << "Hello " << szName << " from " << QThread::currentThreadId();
17 return szName;
18 }
19
20 void run()
21 {
22 QFuture<QString> f3 = QtConcurrent::run(this, &HELLO::hello, QString("Lily"));
23 QFuture<QString> f4 = QtConcurrent::run(g_pThreadPool, this, &HELLO::hello, QString("Sam"));
24
25 f3.waitForFinished();
26 f4.waitForFinished();
27
28 qDebug() << "f3 : " << f3.result();
29 qDebug() << "f4 : " << f4.result();
30 }
31 };
32
33 QString hello(QString szName)
34 {
35 qDebug() << "Hello " << szName << " from " << QThread::currentThreadId();
36 return szName;
37 }
38
39 int main(int argc, char *argv[])
40 {
41 QCoreApplication a(argc, argv);
42
43 QFuture<QString> f1 = QtConcurrent::run(hello, QString("Alice"));
44 QFuture<QString> f2 = QtConcurrent::run(g_pThreadPool, hello, QString("Bob"));
45
46 f1.waitForFinished();
47 f2.waitForFinished();
48
49 qDebug() << "f1 : " << f1.result();
50 qDebug() << "f2 : " << f2.result();
51
52 HELLO h;
53 h.run();
54
55 g_pThreadPool = NULL;
56
57 return a.exec();
58 }