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 }
菜鳥一枚,僅記錄成長!
參考:https://www.cnblogs.com/sherlock-lin/articles/11708966.html
https://blog.csdn.net/luoyayun361/article/details/97150788