最近的項目上用到了關於多線程的知識,本身也比較感興趣,因此就拿了那本《C++ GUI Qt4 編程》來學習。編程
這本書的第14章是關於多線程的知識,使用的Qt版本是Qt4.x。在下用的是最新的Qt 5.2,因此代碼上有一些不兼容,稍加修改就能夠運行了。多線程
Qt的多線程簡單來講就是繼承QThread類,重載run()函數,start()啓動線程。首先來看下書上的第一個例子:(修改版的代碼已上傳,點擊下載)app
class Thread : public QThread { Q_OBJECT public: Thread(QString message = "", QObject *parent = NULL); ~Thread(); void setMessage(QString); QString getMessage(); void stop(); protected: void run(); private: QString message; volatile bool stopped; };
Thread類繼承了QThread類,並實現了run函數。stopped變量前面的volatile聲明stopped爲易失性變量,這樣每次讀取stopped時都是最新的值。函數
繼續看Thread類的實現:學習
Thread::Thread(QString message, QObject *parent) : stopped(false) , QThread(parent) , message(message) { } Thread::~Thread() { this->stop(); this->wait(); qDebug() << this; } void Thread::setMessage(QString message) { this->message = message; } QString Thread::getMessage() { return this->message; } void Thread::stop() { stopped = true; } void Thread::run() { while (!stopped) std::cerr << qPrintable(message); stopped = false; std::cerr << std::endl; }
初始化時將stopped設置爲false,run函數中持續檢查stopped的值,爲true時才退出。ui
Dialog::Dialog(QWidget *parent) : QDialog(parent) { QPushButton *buttonQuit = new QPushButton(QString::fromLocal8Bit("Quit")); connect(buttonQuit, &QPushButton::clicked, this, &Dialog::close); QBoxLayout *layout = new QBoxLayout(QBoxLayout::LeftToRight, this); QStringList list = QString("ABCDEFGHIJKLMN").split("",QString::SkipEmptyParts); foreach (QString name, list) { Thread *thread = new Thread(name, this); QPushButton *button = new QPushButton(QString("Start ")+name, this); mappingTable.insert(button, thread); connect(button, &QPushButton::clicked, this, &Dialog::startOrStopThread); layout->addWidget(button); } layout->addWidget(buttonQuit); this->setLayout(layout); } void Dialog::startOrStopThread() { QPushButton *buttonNow = dynamic_cast<QPushButton*>(sender()); Thread *threadNow = (Thread*)mappingTable[buttonNow]; if (threadNow == NULL) return; if(threadNow->isRunning()) { threadNow->stop(); buttonNow->setText( buttonNow->text().replace(QString("Stop"),QString("Start")) ); } else { threadNow->start(); buttonNow->setText( buttonNow->text().replace(QString("Start"),QString("Stop")) ); } }
在Dialog界面類中,將button與thread實現一一對應的鏈接,在槽函數中就能夠方便的找到對應的線程了。其中mappingTable是QMap<QObject*, QObject*>類型的。this
這樣就能夠方便的實現多個線程的修改,以下圖:spa
另外,第四個例子對我也頗有啓發:線程
TransactionThread::TransactionThread(QObject *parent) : QThread(parent) { start(); } TransactionThread::~TransactionThread() { { QMutexLocker locker(&mutex); while (!transactions.isEmpty()) delete transactions.dequeue(); transactionCondition.wakeOne(); } wait(); } void TransactionThread::addTransaction(Transaction *transaction) { QMutexLocker locker(&mutex); transactions.enqueue(transaction); transactionCondition.wakeOne(); } void TransactionThread::run() { Transaction *transaction = 0; QImage oldImage; forever { { QMutexLocker locker(&mutex); if (transactions.isEmpty()) transactionCondition.wait(&mutex); if (transactions.isEmpty()) break; transaction = transactions.dequeue(); oldImage = currentImage; } emit transactionStarted(transaction->message(), 0); QImage newImage = transaction->apply(oldImage); delete transaction; { QMutexLocker locker(&mutex); currentImage = newImage; if (transactions.isEmpty()) emit allTransactionsDone(); } } } void TransactionThread::setImage(const QImage& image) { QMutexLocker locker(&mutex); currentImage = image; } QImage TransactionThread::getImage() { QMutexLocker locker(&mutex); return currentImage; }
以上爲線程實現的關鍵代碼。在讀取和寫入從線程與主線程共享的變量時,都要使用mutex互斥變量。使用QMutexLocker locker(&mutex)也更方便,在構造是lock,析構時unlock,臨時變量超過了做用域天然被析構,不得不說實現者的方法很巧妙啊。至於transactionCondition.wait(&mutex)則是等待條件。當事務隊列爲空時,等待事務加入,或者析構。加入事務時喚醒便可,即transactionCondition.wakeOne()。3d