說明
Qt中可以有多種使用線程的方式:
- 繼承 QThread,重寫 run() 接口;
- 使用 moveToThread() 方法將 QObject 子類移至線程中,內部的所有使用信號槽的槽函數均在線程中執行;
- 使用 QThreadPool 線程池,搭配 QRunnable;
- 使用 QtConcurrent;
本文跳過第1和第2中方式,介紹后面兩種
線程池
- 創建和銷毀線程需要和OS交互,少量線程影響不大,但是線程數量太大,勢必會影響性能,使用線程池可以這種開銷;
- 線程池維護一定數量的線程,使用時,將指定函數傳遞給線程池,線程池會在線程中執行任務;
(一)QThreadPool和QRunnable
Qt中需要繼承 QRunnable,重寫 run() 方法,並,將其傳遞給線程池 QThreadPool 進行管理
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
void QThreadPool::cancel(QRunnable * runnable);
void QThreadPool::clear();
- 如果,指定的QRunnable還沒有執行,則從隊列中移除
- 清空隊列中還沒有執行的QRunnable;
bool QThreadPool::waitForDone(int msecs = -1);
- 等待所有線程結束並釋放資源(如果需要自動釋放的話);
- msecs指定超時;
- 若所有線程都被移除,則,返回true,否則返回false;
int maxThreadCount() const
void setMaxThreadCount(int maxThreadCount)
- 線程池維護的最大線程數量;
- 設定該值,不會影響已經開始的線程;
- 若沒有設定,默認值是最大線程數,可以用:
QThread::idealThreadCount();
獲取;
int expiryTimeout() const
void setExpiryTimeout(int expiryTimeout)
- 線程的終結超時;
- 沒有開啟,且超過終結時間的線程,會退出,這些線程會根據需要重啟開始,即這些線程不會消失,線程池會重新取出這些線程,開啟或者放入隊列,所謂的終結超時就是重新排列等待隊列;
- 建議在創建線程池后,調用 start() 前設定終結超時;
static QThreadPool * QThreadPool::globalInstance();
- 全局內存池實例;
- 若創建QThreadPool實例,則在實例生存周期內,內存池有效,
代碼示例
//MyRunnable.h
class MyRunnable : public QRunnable
{
public:
MyRunnable(const QString& thread_name);
void run();
private:
QString threadName;
};
//MyRunnable.cpp
#include "myrunnable.h"
MyRunnable::MyRunnable(const QString &thread_name) : threadName(thread_name){}
void MyRunnable::run()
{
qDebug()<<"Start thread id:"<<QThread::currentThreadId();
int count = 0;
while(true)
{
if(count >= 10)
{
break;
}
qDebug()<<threadName<<" Count:"<<count++;
QThread::msleep(500);
}
}
//調用處
MyRunnable* my_runnable = new MyRunnable("1# thread");
my_runnable->setAutoDelete(true);
MyRunnable* my_runnable_2 = new MyRunnable("2# thread");
my_runnable_2->setAutoDelete(true);
threadPool.start(my_runnable);
threadPool.start(my_runnable_2);
(二)QtConcurrent
若有大量工作需要完成,則使用方式1、2、3均可,但是若只有一小段工作,需要在線程中完成,無論是使用QThread,還是moveToThread,更或者,使用QThreadPool,都有大材小用的感覺,這時候,使用 QtConcurrent 就是最佳選擇
下面的說明,以Qt自帶的例子為基礎,並加入部分修改,例子目錄:..\Qt\Qt5.5.1_mingw\Examples\Qt-5.5\qtconcurrent\runfunction
pro文件中添加模塊
QT += concurrent
代碼示例:
QString hello(QString name)
{
qDebug() << "Hello" << name << "from" << QThread::currentThread();
return name;
}
//掉用處
QFuture<QString> f1 = QtConcurrent::run(hello, QString("Alice"));
QFuture<QString> f2 = QtConcurrent::run(&threadPool, hello, QString("Bob"));
f1.waitForFinished();
f2.waitForFinished();
qDebug()<<f1.result();
qDebug()<<f2.result();
輸出如下:
Hello "Alice" from QThread(0x1b1562a8, name = "Thread (pooled)")
Hello "Bob" from QThread(0x1b156248, name = "Thread (pooled)")
"Alice"
"Bob"
(1)說明:
QFuture<T> QtConcurrent::run(Function function, ...);
//QtConcurrent::run(QThreadPool::globalInstance(), function, ...);
QFuture<T> QtConcurrent::run(QThreadPool * pool, Function function, ...);
QtConcurrent::run() 方法的第一個參數是線程池,可以指定線程池,若沒有指定,則使用全局線程池;
(2)執行普通函數,並傳參,獲取返回值
參考上例
- 將需要傳遞的參數依次跟在函數名后面;
- 使用 QFuture<T> 的 result() 方法獲取返回值;
(3)執行非const成員函數
QFuture<QString> f1 = QtConcurrent::run(this, &MainWindow::Hello, QString("Alice"));
QFuture<QString> f2 = QtConcurrent::run(&threadPool, this, &MainWindow::Hello, QString("Bob"));
- 線程池和傳參以及返回值相同;
- 既然是成員,則需要指定實例,且是非const,則可能需要修改成員,在函數名前傳入實例指針或者實例的應用;
(4)執行const成員
// call 'QList<QByteArray> QByteArray::split(char sep) const' in a separate thread
QByteArray bytearray = "hello world";
QFuture<QList<QByteArray> > future = QtConcurrent::run(bytearray, &QByteArray::split, ',');
...
QList<QByteArray> result = future.result();
const成員不會使用非const成員,所以,傳入指針/引用或或者形參並沒有區別,上例中,就是傳入了形參