1. 程式人生 > >QT多執行緒淘酒,持續更新

QT多執行緒淘酒,持續更新

第一階段

首先必須區分三個概念:

主執行緒:在程式初始化完成後,主執行緒就進入了main()函式開始執行應用程式碼,一般在主執行緒上構建介面物件並呈現之,然後就進入了事件迴圈以處理各類訊息(控制元件繪製、使用者輸入、系統輸出等訊息)。這就是熟知的事件驅動模型。

工作執行緒:也就是子執行緒.

以QThread為例說明,QThread 物件關聯(依附)於建立它的執行緒,當QThread物件呼叫start()方法時,會預設呼叫它的run()方法。在run()是子執行緒中。因此在QThread的構造方法中建立的物件都不屬於子執行緒,而屬於建立QThread物件的執行緒。

QObject::moveToThread(QThread *targetThread) 為例說明,只要targetThread在start()以後,該QObject的槽函式都在targetThread子執行緒中執行。此時targetThread的run()函式已經進入訊息迴圈(非while(1))。

關聯(依附)執行緒:對於物件obj而言,obj.thread()方法可以返回該物件所依附的執行緒。

改變物件所依附的執行緒,用QObject::moveToThread(QThread *targetThread) 這樣obj及其子物件就依附到了targetThread執行緒,它的訊號槽處理也在targetThread執行緒中處理。如果obj有父物件,則移動失敗。

碰到這樣的問題:QObject: Cannot create children for a parent that is in a different thread.

是因為obj的Init方法中子物件沒有確定其父物件所造成的。

 myserial = new QSerialPort(); 
改為
 myserial = new QSerialPort(this);
QSerialPort(QObject *parent = Q_NULLPTR) 不指定parent,預設是Q_NULLPTR,
好的習慣是子物件建立時自覺加上this,這樣方便Qt管理,當銷燬父物件,檢查子物件列表,不會出現遺落,造成記憶體洩漏(記憶體洩漏
多指是程式內部,該銷燬的記憶體沒有銷燬)。

在使用繼承QThread的方法來建立新執行緒,必須瞭解一條規則:
		QThread中只有run()方法是在新執行緒,其他所有方法都在建立QThread的執行緒裡。

在UI主執行緒中呼叫QThread的非run()方法,和執行UI執行緒普通方法無差別。但是,如果這個函式要對QThread的某個變數進行變更,而
這個變數在run()函式也用的話,這時需要考慮加鎖。

QT子執行緒寫法技巧:
1、儘量不要在run()方法中寫死迴圈,對於要持續做的事情,可以做個定時器,banding到timeout訊號上;
2、寫一個object,用moveToThread()方法關聯到目標執行緒;
3、對於object,對外的所有操作,全部用訊號槽。通過槽接收外部的呼叫操作(其他執行緒發出訊號,槽函式在此執行緒中得到相應);通過訊號
傳送資料給外部(比如接收到的資料);
4、需要在子執行緒分配的資源,比如QSerialPort,可以通過該object的某個槽函式(如slotInit())中進行,將其banding到執行緒物件的started()
訊號上。也就排除了這個問題:QObject: Cannot create children for a parent that is in a different thread.
5、程式退出,如何銷燬object?
6、對於執行緒物件(why),以及Move到執行緒裡的物件(否則move失敗),都不要設定parent。


/******************************************************/

 
第二階段——執行緒管理

一、執行緒優先順序 enum QThread::Priority       
該列舉型別表明作業系統如何去安排新建立的執行緒。
QThread::IdlePriority——只有在沒有其他執行緒在running時才排程
QThread::LowestPriority《QThread::LowPriority《QThread::NormalPriority<<QThread::HighPriority<<
QThread::HighestPriority<<QThread::TimeCriticalPriority 排程頻率有低到高到儘可能頻繁的排程
QThread::Inheritpriority:default used the same priority as the creating thread
 
二、執行緒啟動
    啟動呼叫start(Priority priority=Inheritpriority),呼叫後會執行run(),但在run()函式執行前
還會發出started()訊號——初始化好用的很。 執行緒是作業系統排程的最小單位,根據優先順序排程。優先順序引數的
效果還是取決於作業系統的排程策略。特別是哪些不支援執行緒優先順序的系統,優先順序會被忽略。
-------具體看

三、執行緒執行
 int exec()    進入事件迴圈並等待直到呼叫exit()。返回值是通過呼叫exit()來獲得,如果呼叫成功則返回0;
 virtual void run()    ---執行緒的開始,一般用while(1)
    

、執行緒退出
void quit()
 告訴執行緒事件迴圈退出,返回0表示成功===呼叫QThread::exit();
void exit(int returnCode = 0)
呼叫這個函式後,執行緒離開事件迴圈後返回。

 

 
危險的void termiante()   終止執行緒,執行緒可能會立即被終止也可能不會,這取決於作業系統的排程策略,使用terminate()之後
再使用QThread::wait()確保萬無一失。
在非必要時,不使用這一招。 

/*************************************************************************************/

 

 
第三部分——執行緒同步問題
使用執行緒的主要想法是希望它們可以儘可能地併發執行,而在一些關鍵點上執行緒之間需要停止或等待。
方法1:使用全域性變數 利用QMutex實現Qt執行緒間共享資料——共享記憶體
方法2:使用signal/slot機制,把資料從一個執行緒傳遞到另一個執行緒。
方法3:環形緩衝區(ring buffer), 環形佇列(ring queue)多用於2個執行緒之間傳遞資料,是標準的先入先出(FIFO)模型。
一般來說,對於多執行緒共享資料,需要使用Mutex來同步,如此共享資料才不至於發生不可預測的修改/讀取,雖然
Mutex的使用也帶來了額外的系統開銷,而引入ring buffer/queue,也是為了有效地解決這個問題。 ring buffer
/queue,因其特殊的結構及演算法,可用於2個執行緒共享資料的同步,而且必須遵循A執行緒push in, B執行緒pull out的
原則。  採用環形緩衝區/佇列這種機制,另一個好處是A push完資料後可以馬上接收另一個數據,當輸入資料較快
時,也不會造成因資料阻塞而丟包。
執行緒A               媒介             執行緒B
data in  --》ring buffer/queue---> data out
ring buffer/queue 原理見於另一篇文章——Ring Buffer/Queue原理。

QT訊號與槽
這是三個相關的連線函式
法1、[static] QMetaObject::Connection QObject::connect(const QObject *sender,\
 const char *signal, const QObject *receiver, const char *method, 
Qt::ConnectionType type = Qt::AutoConnection)
這是靜態函式,你必須使用SIGNAL()和SLOT()巨集命令來指定引數signal和method。
例子QObject::connect(scrollBar, SIGNAL(valueChanged(int)), label,  SLOT(setNum(int)));

 
法2、[static]QMetaObject::Connection QObject::connect(const QObject *sender, const QMetaMethod &signal,\
 const QObject *receiver, const QMetaMethod &method,\
 Qt::ConnectionType type = Qt::AutoConnection)

 

這也是一個靜態函式,與法1類似,只是signal和method都是QMetaMethod型別的物件,

法3、QMetaObject::Connection QObject::connect(const QObject *sender, \
const char *signal, const char *method, Qt::ConnectionType 
type = Qt::AutoConnection) const
與Equivalent to connect(sender, signal, this, method, type)相同

法4、[static] QMetaObject::Connection QObject::connect(const QObject *sender,\
 PointerToMemberFunction signal, const QObject *receiver, \
PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection)

例子 QObject::connect(lineEdit, &QLineEdit::textChanged, label, &QLabel::setText);

這是執行緒安全的。一個訊號和多個槽連線,但發出emit 訊號,這些槽按照connection順序來啟用。
1、它支援編譯器件檢查訊號和槽是否存在,它們的型別,及Q_QBJECT是否丟失
2、引數能被teypdef或不同名稱空間指定
3、如果有隱式轉換的引數,會自動轉換型別。
4、不僅能指定槽函式,還可以連結QObject下的任何成員函式。
雖然有五種:

 
先講一講引數 Qt::ConnectionType  type (5種)
預設是Qt::AutoConnection  就是自動根據情況來,在訊號發出時決定。
Qt::DirectConnection:接收者receiver和emit訊號是在一個執行緒中,用直接連線,
發出訊號後,立即呼叫槽函式。
Qt::QueuedConnection:槽函式在CPU時間片輪到接受者reciver所線上程時呼叫,槽
函式執行在接收者reciver所線上程。------這就是我們在多執行緒中用moveToThread()來
改變依附執行緒的原因。改變後,該objcet的槽函式就可以在子執行緒中執行,減少UI執行緒
的耗時操作。----實現跨執行緒
Qt::BlockingQueuedConnection:和Qt::QueuedConnection類似,不同之處在於
emit發出訊號的執行緒阻塞直到槽函式返回。這種型別當然不合適發出訊號和receiver在
同一個執行緒的情況,否則就發生死鎖deadLock。
Qt::UniqueConnection:是以上連線型別的OR結合版,如果有連結存在,就失敗。

槽函式
是普通的C++成員函式,可以被正常呼叫,它們唯一的特殊性就是可以與訊號相連。
當與之關聯的訊號被髮射時,這個槽就會被呼叫。
槽函式與普通成員函式一言,有public、private、protected等特性。
元物件編譯器moc(meta object compiler)對C++檔案中的類宣告進行分析併產生用於
初始化元物件的C++程式碼,元物件包含全部訊號和槽的名字以及指向這些函式的指標。
moc讀C++原始檔,如果發現有Q_OBJECT巨集宣告的類,它就會生成另外一個C++源
檔案