效能特性測試系列4——QT執行緒與std::thread(下)之QThread
其實用法早就總結了,但是因為自身事務原因,一直沒有比較他們間的效率,甚至各種互斥量,條件變數之間的效率,更多是集中看了下他們各自的api和特點,所以估計以後還需要補充或者是另開一篇來講= =。
序
QThread的執行緒用法上與std::thread相比有較大的區別,4.4版本之前是繼承的方式來使用執行緒(個人猜測可能是因為那會兒c++11還沒出來,std::function和std::bind沒有,所以繼承是實現訊息回撥比較方便的方式,當然僅僅是猜測,有興趣可以查證),但4.4之後開始,官方建議不要再用繼承的方式來使用執行緒,而是通過訊號槽的方式來取代。
測試環境:Qt5.7,vs2015。
一、QThread:
如下圖所示:
看不清楚圖可以直接在官網看:傳送門
1、繼承自QObject(截圖沒截上)
2、啟動執行緒:start()函式,啟動後呼叫run()函式,run()執行完之後退出執行緒。
3、wait:有點類似std::thread 的join,但是需要指定時間,並且不是執行緒run函式結束後自動返回,如果不指定,預設會一直等待。所以我一般在用的過程中,需要退出的時候(或者呼叫quit),再wait。
二、兩種用法:
既然QThread有兩種用法,那麼就簡單介紹下吧:
圖裡已經說得很清楚了,那我就不在多說:
標頭檔案:
class QtThreadFuncByThread : public QThread
{
public:
void SetLoop(int loop);
int GetSum();
protected:
virtual void run();
private:
int _Loop = 0;
int _Sum = 0;
};
cpp
void QtThreadFuncByThread::SetLoop(int loop)
{
this->_Loop = loop;
}
int QtThreadFuncByThread::GetSum()
{
return this->_Sum;
}
void QtThreadFuncByThread::run()
{
for(int i = 0; i < this->_Loop; ++i)
{
this->_Sum++;
}
int id =(int)QThread::currentThreadId();
emit Log::GetInstance()->LogStr(QString("qt繼承方式子執行緒id: %1;").
arg(id));
}
使用:
QtThreadFuncByThread thread;
thread.SetLoop(loop);
thread.start();
thread.wait(100);
2、訊號槽方式(推薦用法):
至於為啥推薦呢,直接給個傳送門吧:傳送門
至於程式碼,官網給了例子:傳送門
我再寫個簡化版的:
QThread * thread = new QThread;
QtThreadFuncClass* funcclass = new QtThreadFuncClass;
funcclass->SetLoop(loop);
funcclass->moveToThread(thread);
QObject::connect(thread, &QThread::started, funcclass, &QtThreadFuncClass::ThreadFunc, Qt::DirectConnection);
thread->start();
thread->wait(100);
簡而言之,你需要做的是把QObject物件,movetothread去,否則你呼叫的訊號槽仍然是在主執行緒。
二、互斥量,鎖,條件變數,原子操作及其他:
其實std::thread有的那些互斥量,自解鎖,條件變數,future,原子操作等,Qt裡面也能找到對應的類,只是用法,和一些細節性的功能不太一致,其他大致都是相似的,所以我就簡要的整理和彙總下:
1、互斥量與自解鎖:
如下圖所示:
qt的互斥量只有簡單的QMutex,當然,某種程度上是std::thread裡那幾種結合體,自解鎖也只有一種,如果不記得std::thread有哪些,請看我上一篇。用法比較簡單,我就不貼程式碼了。
2、讀寫鎖與其自解鎖:
所謂讀寫鎖,顧名思義,即讀鎖定狀態與寫鎖定狀態是不一樣的。例如指定某段讀取區域為lockForRead(),則表示這段程式碼僅僅是對資源進行讀取,沒有改變,所有執行緒可以共享該資源,無需阻塞;另一段改寫某資源區域為LockForWrite(),則表示這段程式碼需要改寫資源,其他執行緒需要阻塞,同時,lockForRead()鎖定的讀取資原始碼也會被鎖住,別的執行緒無論是讀還是寫會阻塞。
3、訊號量:
圖中其實把函式介紹的比較清楚了,即先指定一定數量的訊號量,在實際用的過程中可用於一個類似生產者消費者模式,一個執行緒負責生產,如果訊號量沒有空位置了,rlease(加一個位置),另一個執行緒負責消費,如果有空位置,就消費一個(acquire)。
4、條件變數:
前面兩個讀寫鎖和訊號量這些還和std::thread提供的機制有區別(因為沒有,當然,想實現也可以用互斥量這些自己寫一個)。條件變數幾乎是沒啥差異,用法也都差不多,函式已經在圖中列出來了。
5、原子操作:
官網傳送門:傳送門
一共就如圖所示只有三種,int,integer,pointer,用法也沒太大的區別,不記得在上一篇有沒有說過,原子物件本身也是帶鎖的,多執行緒訪問的時候不用擔心上鎖問題,是為細粒度鎖(mutex這些是粗粒度鎖)。
6、future:
qfuture依賴於Qt Concurrent,下圖接上圖:
官網傳送門:傳送門
簡單的說就是一個非同步方法,類似std::async,只執行一次的非同步方法,具體概念用法圖中已經解釋了。
下圖解釋下QFuture和QFutureWatcher關係(官網例子):
7、QThreadPool:
其實這個不算執行緒機制,不應該放上來,隨便看看:
三、一張圖:
該圖能較好說明QThread的一些用法及生命週期。
如下圖所示
參考資料:傳送門
本次其實還有許多的東西可以說,只是做了個簡單的圖表,慢慢來,現在沒有時間補充,總結完這些,其實理解上的話也比較簡單,暫時就先這樣,當做挖個坑吧= =。
ps:基本上,執行緒的一些特性就先這樣,以後有時間再說,下期是網路程式設計,Qt的tcp和udp,以及asio的tcp,udp,時間未知。