今天回想研究生期间做的项目,用到了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中的不同信号,来调用不同的槽函数。