閱讀608 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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() {
}


然後在Transaction Tread類中,相當於第二個線程,通過single-slot向主線程發送信號。


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

  上一篇:go Java中接口定義成員變量
  下一篇:go 微信之父張小龍:怎樣做簡單的產品經理?三