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