QT中的多線程-與主線程通信
今天回想研究生期間做的項目,用到了Qt的多線程通信,當時一點都不懂,就這照貓畫虎地寫,如今因為上次麵試中問到了,覺得得好好準備下:
Qt 程序開始執行時,唯一的一個線程 —— 主線程 (main thread)也開始執行。主線程是唯一的,因為隻有它才能創建 QApplication 或者是 QCoreApplication 對象,隻有它才能通過應用程序對象調用 exec( ) 函數,隻有它才能在 exec( ) 執行完畢後等待並處理事件。
主線程可以通過創建 QThread 子類對象開啟一個新的線程,如果這些線程間需要相互通訊,它們可以使用共享變量,同時使用 mutexes,read-write locks,semaphores 或者 wait conditions 一些方法保持共享變量訪問的同步性。但是由於這些技術可能鎖定 event loop,同時還會凍結用戶界麵,所以其中沒有一個能完成與主線程之間的通訊。
完成第二線程(secondary thread)與主線程之間的通訊的方法是:跨線程間的 signal-slot 連接。signal 一旦發出,其對應的 slot 函數便立即執行,這種連接是一種同步的機製。
但是當我們將不同線程中的對象連接在一起時,這種 signal-slot 通訊機製變得“不同步”(asynchronous)。signal-slot 機製的底層實現是傳遞一個 event,然後 slot 由 receiver 對象所在的線程中的 event loop 調用。默認情況下,一個 QObject 對象存在於創建它的線程中,但是任何時刻,調用 QObject : : moveToThread( ) 函數可以改變這種關係。
當時我們的程序中其實有兩個大類,一個是Transaction類,一個是Transaction Thread類,可以看到Transaction是個抽象類,裏麵的純虛函數Execute雖然在子類的實現中都是空的,但是目的就是為了讓Transaction成為一個抽象類,
Transaction類:
class Transaction { public: virtual ~Transaction() { } virtual void Execute() = 0; int getTransactID(); protected: int transactID; }; class LoginTransaction: public Transaction { public: LoginTransaction(); void Execute(); private: };
在cpp文件中對LoginTransaction的構造函數和必須實現的接口做定義(雖然是空的。。。)
int Transaction::getTransactID() { return transactID; } LoginTransaction::LoginTransaction() { transactID = TR_LOGIN_SUCCESS; } void LoginTransaction::Execute() { }
TransactionTread.h文件
class TransactionThread: public QThread { Q_OBJECT public: TransactionThread(); virtual ~TransactionThread(); void addTransaction(Transaction *tr); protected: void run(); signals: void loginSuccess(); private: QQueue<Transaction*> transactQueue; QWaitCondition transactAdded; QMutex mutex; private: void ProcessTransact(Transaction* tr);在這個類中,run 函數在自己的線程中執行,其它的函數則從主線程調用。在這個類中,維護著一個 transaction 隊列,其中的每個 transaction 將一個接一個地被執行。
TransactionTread.cpp文件
Transaction * const EndTransaction = 0; TransactionThread::TransactionThread() { start(); } TransactionThread::~TransactionThread() { { QMutexLocker locker(&mutex); while (!transactQueue.isEmpty()) delete transactQueue.dequeue(); transactQueue.enqueue(EndTransaction); transactAdded.wakeOne(); } wait(); } void TransactionThread::addTransaction(Transaction *tr) { QMutexLocker locker(&mutex); transactQueue.enqueue(tr); transactAdded.wakeOne(); } void TransactionThread::run() { Transaction *tr = 0; forever { { QMutexLocker locker(&mutex); if (transactQueue.isEmpty()) transactAdded.wait(&mutex); tr = transactQueue.dequeue(); if (tr == EndTransaction) break; } ProcessTransact(tr); // // if (tr->getTransactID() != TR_ADDED || ) // delete tr; } } void TransactionThread::ProcessTransact(Transaction *tr) { switch (tr->getTransactID()) { case TR_LOGIN_SUCCESS: ProcessTrLogin(tr); break; case TR_LOGIN_FAIL: ProcessTrLoginFail(tr); break; case TR_ADDED: ForwardTransact(tr); //ProcessTrAdded(tr); break; case TR_ADDNORMAL: //ForwardTransact(tr); ProcessTrAddNormal(tr); break; case TR_STATUS: ProcessTrStatus(tr); break; case TR_VIEWCONTACT: ProcessTrViewContact(tr); break; case TR_NEW_CONTACT: ProcessTrNewContact(tr); break; case TR_DELETECONTACT: ForwardTransact(tr); break; case TR_BEDELETED: ForwardTransact(tr); break; case TR_NEW_IM: ProcessTrNewIM(tr); break; case TR_INS_CONTACT: ProcessTrInsContact(tr); break; case TR_PHONE_LOST: ProcessTrPhoneLost(tr); break; default: break; } } void TransactionThread::ProcessTrLoginFail(Transaction *tr) { tr->Execute(); emit loginFailure(); } void TransactionThread::ProcessTrLogin(Transaction *tr) { tr->Execute(); emit loginSuccess(); }
調用 QThread : : start( ) 開啟將要執行 transaction 的線程。在析構函數中,清空隊列,將一個特殊的 EndTransaction 加入隊列。喚醒線程,並使用 QThread : : wait( ) 等待線程結束。如果沒有 wait( ),當其它的線程訪問類中的成員變量時,程序有可能崩潰。 在析構函數中,QMutexLocker 的析構造函數將被調用。其中將 mutex 解鎖,在 wait( ) 之前解鎖這很重要,否則將引起死鎖的可能性(第二線程一直等待 mutex 被解鎖,而主線程一直等待第二線程完成而操持 mutex 不放)。
QWaitCondition : : wakeOne( ) 函數喚醒一個正在等待某個條件的線程。被喚醒的線程取決於操作係統的排程策略,並不能控製和提前預知哪個線程將被喚醒。如果需要喚醒某個指定的線程,通常需要使用不同的等待條件,使用不同的線程專門等待不同的等待條件。
addTransaction( ) 函數將一個 transaction 添加到隊列中,並喚醒 transaction 線程。所有訪問 transactions 的成員變量都由一個 mutex 保護,因為在第二線程遍曆隊列中的 transaction 時主線程可能修改這些變量。
run函數中定義了不同的執行方法,獲取transaction的id,然後進行不同的侗族,也就是發射出不同的信號。然後在主線程中,connect這些信號到主線程的槽函數中:
UIControl::UIControl(Manager *p, QApplication* a) { app = a; manager = p; loginpage = NULL; waitpage = NULL; main = NULL; msgbox = new MessageBox(this); tr_queue = new TransactionThread(); connect(tr_queue, SIGNAL(loginSuccess()), this, SLOT(ShowMainFrame()));沒寫完,UIControl就是個主界麵,通過tr_queue中的不同信號,來調用不同的槽函數。
最後更新:2017-04-03 12:56:38