Qt對線程提供了支持,,基本形式有獨(dú)立于平臺的線程類,、線程安全方式的事件傳遞和一個全局Qt庫互斥量允許你可以從不同的線程調(diào)用Qt方法。 這個文檔是提供給那些對多線程編程有豐富的知識和經(jīng)驗(yàn)的聽眾的,。推薦閱讀: 警告:所有的GUI類(比如,,QWidget和它的子類),操作系統(tǒng)核心類(比如,,QProcess)和網(wǎng)絡(luò)類都不是線程安全的,。 QRegExp使用一個靜態(tài)緩存并且也不是線程安全的,即使通過使用QMutex來保護(hù)的QRegExp對象,。 線程類最重要的類是QThread,,也就是說要開始一個新的線程,就是開始執(zhí)行你重新實(shí)現(xiàn)的QThread::run(),。這和Java的線程類很相似。 為了寫線程程序,,在兩個線程同時希望訪問同一個數(shù)據(jù)時,,對數(shù)據(jù)進(jìn)行保護(hù)是很必要的。因此這里也有一個QMutex類,,一個線程可以鎖定互斥量,,并且在它鎖定之后,其它線程就不能再鎖定這個互斥量了,,試圖這樣做的線程都會被阻塞直到互斥量被釋放,。例如: class MyClass
{
public:
void doStuff( int );
private: QMutex mutex; int a; int b;
};
// 這里設(shè)置a為c,b為c*2,。
void MyClass::doStuff( int c )
{ mutex.lock();
a = c;
b = c * 2;
mutex.unlock();
}
這保證了同一時間只有一個線程可以進(jìn)入MyClass::doStuff(),,所以b將永遠(yuǎn)等于c * 2。 另外一個線程也需要在一個給定的條件下等待其它線程的喚醒,,QWaitCondition類就被提供了,。線程等待的條件QWaitCondition指出發(fā)生了什么事情,阻塞將一直持續(xù)到這種事情發(fā)生,。當(dāng)某種事情發(fā)生了,,QWaitCondition可以喚醒等待這一事件的線程之一或全部。(這和POSIX線程條件變量是具有相同功能的并且它也是Unix上的一種實(shí)現(xiàn),。)例如: #include <qapplication.h> #include <qpushbutton.h> // 全局條件變量 QWaitCondition mycond;
// Worker類實(shí)現(xiàn) class Worker : public QPushButton, public QThread {
Q_OBJECT public: Worker(QWidget *parent = 0, const char *name = 0) : QPushButton(parent, name) { setText("Start Working"); // 連接從QPushButton繼承來的信號和我們的slotClicked()方法 connect(this, SIGNAL(clicked()), SLOT(slotClicked())); // 調(diào)用從QThread繼承來的start()方法……這將立即開始線程的執(zhí)行 QThread::start(); } public slots: void slotClicked() { // 喚醒等待這個條件變量的一個線程 mycond.wakeOne(); } protected: void run() { // 這個方法將被新創(chuàng)建的線程調(diào)用…… while ( TRUE ) { // 鎖定應(yīng)用程序互斥鎖,,并且設(shè)置窗口標(biāo)題來表明我們正在等待開始工作 qApp->lock(); setCaption( "Waiting" ); qApp->unlock(); // 等待直到我們被告知可以繼續(xù) mycond.wait(); // 如果我們到了這里,我們已經(jīng)被另一個線程喚醒……讓我們來設(shè)置標(biāo)題來表明我們正在工作 qApp->lock(); setCaption( "Working!" ); qApp->unlock(); // 這可能會占用一些時間,幾秒,、幾分鐘或者幾小時等等,,因?yàn)檫@個一個和GUI線程分開的線程,在處理事件時,,GUI線程不會停下來…… do_complicated_thing(); } } }; // 主線程——所有的GUI事件都由這個線程處理,。 int main( int argc, char **argv ) { QApplication app( argc, argv ); // 創(chuàng)建一個worker……當(dāng)我們這樣做的時候,這個worker將在一個線程中運(yùn)行 Worker firstworker( 0, "worker" ); app.setMainWidget( &worker ); worker.show(); return app.exec(); } 只要你按下按鈕,,這個程序就會喚醒worker線程,,這個線程將會進(jìn)行并且做一些工作并且然后會回來繼續(xù)等待被告知做更多的工作。如果當(dāng)按鈕被按下時,,worker線程正在工作,,那么就什么也不會發(fā)生。當(dāng)線程完成了工作并且再次調(diào)用QWaitCondition::wait(),,然后它就會被開始,。 線程安全的事件傳遞在Qt中,一個線程總是一個事件線程——確實(shí)是這樣的,,線程從窗口系統(tǒng)中拉出事件并且把它們分發(fā)給窗口部件,。靜態(tài)方法QThread::postEvent從線程中傳遞事件,而不同于事件線程,。事件線程被喚醒并且事件就像一個普通窗口系統(tǒng)事件那樣在事件線程中被分發(fā),。例如,你可以強(qiáng)制一個窗口部件通過如下這樣做的一個不同的線程來進(jìn)行重繪: QWidget *mywidget; QThread::postEvent( mywidget, new QPaintEvent( QRect(0, 0, 100, 100) ) ); 這(異步地)將使mywidget重繪一塊100*100的正方形區(qū)域,。 Qt庫互斥量Qt庫互斥量提供了從線程而不是事件線程中調(diào)用Qt方法的一種方法,。例如: QApplication *qApp; QWidget *mywidget; qApp->lock(); mywidget->setGeometry(0,0,100,100); QPainter p;
p.begin(mywidget);
p.drawLine(0,0,100,100);
p.end();
qApp->unlock();
在Qt中沒有使用互斥量而調(diào)用一個函數(shù)通常情況下結(jié)果將是不可預(yù)知的。從另外一個線程中調(diào)用Qt的一個GUI相關(guān)函數(shù)需要使用Qt庫互斥量,。在這種情況下,,所有可能最終訪問任何圖形或者窗口系統(tǒng)資源的都是GUI相關(guān)的。使用容器類,,字符串或者輸入/輸出類,,如果對象只被一個線程使用就不需要任何互斥量了。 告誡當(dāng)進(jìn)行線程編程時,,需要注意的一些事情: - 當(dāng)使用Qt庫互斥量的時候不要做任何阻塞操作,。這將會凍結(jié)事件循環(huán)。
- 確認(rèn)你鎖定一個遞歸QMutex的次數(shù)和解鎖的次數(shù)一樣,,不能多也不能少,。
- 在調(diào)用除了Qt容器和工具類的任何東西之前鎖定Qt應(yīng)用程序互斥量。
- 謹(jǐn)防隱含地共享類,,你應(yīng)該避免在線程之間使用操作符=()來復(fù)制它們,。這將會在Qt的未來主要的或次要的發(fā)行版本中進(jìn)行改進(jìn),。
- 謹(jǐn)防那些沒有被設(shè)計為線程安全的Qt類,例如,,QPtrList的應(yīng)用程序接口就不是線程安全的并且如果不同的線程需要遍歷一個QPtrList,,它們應(yīng)該在調(diào)用QPtrList::first()之前鎖定并且在到達(dá)終點(diǎn)之后解鎖,而不是在QPtrList::next()的前后進(jìn)行鎖定和解鎖,。
- 確認(rèn)只在GUI線程中創(chuàng)建的繼承和使用了QWidget,、QTimer和QSocketNotifier的對象。在一些平臺上,,在某個不是GUI線程的線程中創(chuàng)建這樣的對象將永遠(yuǎn)不會接受到底層窗口系統(tǒng)的事件,。
- 和上面很相似,只在GUI線程中使用QNetwork類,。一個經(jīng)常被問到的問題是一個QSocket是否可以在多線程中使用,。這不是必須得,因?yàn)樗械腝Network類都是異步的,。
- 不要在不是GUI線程的線程中試圖調(diào)用processEvents()函數(shù),。這也包括QDialog::exec()、QPopupMenu::exec(),、QApplication::processEvents()和其它一些,。
- 在你的應(yīng)用程序中,不要把普通的Qt庫和支持線程的Qt庫混合使用,。這也就是說如果你的程序使用了支持線程的Qt庫,,你就不應(yīng)該連接普通的Qt庫、動態(tài)的載入普通Qt庫或者動態(tài)地連接其它依賴普通Qt庫的庫或者插件,。在一些系統(tǒng)上,這樣做會導(dǎo)致Qt庫中使用的靜態(tài)數(shù)據(jù)變得不可靠了,。
QT通過三種形式提供了對線程的支持,。它們分別是,一,、平臺無關(guān)的線程類,,二、線程安全的事件投遞,,三,、跨線程的信號-槽連接。這使得開發(fā)輕巧的多線程 Qt程序更為容易,,并能充分利用多處理器機(jī)器的優(yōu)勢,。多線程編程也是一個有用的模式,它用于解決執(zhí)行較長時間的操作而不至于用戶界面失去響應(yīng),。 Qt 線程類 Qt 包含下面一些線程相關(guān)的類: QThread 提供了開始一個新線程的方法 QThreadStorage 提供逐線程數(shù)據(jù)存儲 QMutex 提供相互排斥的鎖,,或互斥量 QMutexLocker 是一個便利類,,它可以自動對QMutex加鎖與解鎖 QReadWriteLock 提供了一個可以同時讀寫操作的鎖 QReadLocker與QWriteLocker 是便利類,它自動對QReadWriteLock加鎖與解鎖 QSemaphore 提供了一個整型信號量,,是互斥量的泛化 QWaitCondition 提供了一種方法,,使得線程可以在被另外線程喚醒之前一直休眠。Qt 高級線程類 QtConcurrent 開啟線程事務(wù) QFutureWatcher 觀測線程狀態(tài) QFuture 線程啟動類 QThread創(chuàng)建線程 為創(chuàng)建一個線程,,子類化QThread并且重寫它的run()函數(shù),,例如: class MyThread : public QThread { Q_OBJECT protected: void run(); }; void MyThread::run() { ... } 之后調(diào)用start,Qt即可創(chuàng)建一個線程,,并在線程中執(zhí)行run()函數(shù)中代碼,,注意UI非線程安全的。 QtConcurrent創(chuàng)建線程 QtConcurrent 創(chuàng)建線程的方法比較多,, 而且QtConcurrent 本身比較特殊,,若系統(tǒng)有空閑線程時,它會調(diào)度空閑線程,,無空閑線程時將會創(chuàng)建一個線程,。 (注意:QtConcurrent 創(chuàng)建線程歸QthreadPool管理,若超過最大線程數(shù),,將會進(jìn)入隊列等待),,QtConcurrent創(chuàng)建線程的方法多種,以下舉例map函數(shù): QImage scale(const QImage &image) { qDebug() < < "Scaling image in thread" << QThread::currentThread(); return image.scaled(QSize(100, 100), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } int main(int argc, char *argv[]) { QApplication app(argc, argv); const int imageCount = 20; // Create a list containing imageCount images. QList images; for (int i = 0; i < imageCount; ++i) images.append(QImage(1600, 1200, QImage::Format_ARGB32_Premultiplied)); // Use QtConcurrentBlocking::mapped to apply the scale function to all the // images in the list. QList thumbnails = QtConcurrent::blockingMapped(images, scale); return 0; }
Qt 線程同步 QMutex, QReadWriteLock, QSemaphore, QWaitCondition 提供了線程同步的手段,。使用線程的主要想法是希望它們可以盡可能并發(fā)執(zhí)行,,而一些關(guān)鍵點(diǎn)上線程之間需要停止或等待。例如,,假如兩個線程試圖同時訪問同一個 全局變量,,結(jié)果可能不如所愿。
QMutex QMutex 提供相互排斥的鎖,,或互斥量,。在一個時刻至多一個線程擁有mutex,假如一個線程試圖訪問已經(jīng)被鎖定的mutex,那么它將休眠,直到擁有mutex的線程對此mutex解鎖,。Mutexes常用來保護(hù)共享數(shù)據(jù)訪問,。 QReadWriterLock QReadWriterLock 與QMutex相似,除了它對 “read”,”write”訪問進(jìn)行區(qū)別對待,。它使得多個讀者可以共時訪問數(shù)據(jù),。使用QReadWriteLock而不是QMutex,可以使得多線程程序更具有并發(fā)性,。 QReadWriteLock lock; void ReaderThread::run() { lock.lockForRead(); read_file(); lock.unlock(); } void WriterThread::run() { lock.lockForWrite(); write_file(); lock.unlock(); } QSemaphore QSemaphore 是QMutex的一般化,,它可以保護(hù)一定數(shù)量的相同資源,與此相對,,一個mutex只保護(hù)一個資源,。下面例子中,,使用QSemaphore來控制對環(huán)狀緩 沖的訪問,此緩沖區(qū)被生產(chǎn)者線程和消費(fèi)者線程共享,。生產(chǎn)者不斷向緩沖寫入數(shù)據(jù)直到緩沖末端,,再從頭開始。消費(fèi)者從緩沖不斷讀取數(shù)據(jù),。信號量比互斥量有更好 的并發(fā)性,,假如我們用互斥量來控制對緩沖的訪問,那么生產(chǎn)者,,消費(fèi)者不能同時訪問緩沖,。然而,我們知道在同一時刻,,不同線程訪問緩沖的不同部分并沒有什么 危害,。 const int DataSize = 100000; const int BufferSize = 8192; char buffer[BufferSize]; QSemaphore freeBytes(BufferSize); QSemaphore usedBytes; class Producer : public QThread { public: void run(); }; void Producer::run() { qsrand(QTime(0,0,0).secsTo(QTime::currentTime())); for (int i = 0; i < DataSize; ++i) { freeBytes.acquire(); buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4]; usedBytes.release(); } } class Consumer : public QThread { public: void run(); }; void Consumer::run() { for (int i = 0; i < DataSize; ++i) { usedBytes.acquire(); fprintf(stderr, "%c", buffer[i % BufferSize]); freeBytes.release(); } fprintf(stderr, "\n"); } int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); Producer producer; Consumer consumer; producer.start(); consumer.start(); producer.wait(); consumer.wait(); return 0; } QWaitCondition QWaitCondition 允許線程在某些情況發(fā)生時喚醒另外的線程。一個或多個線程可以阻塞等待一QWaitCondition ,用wakeOne()或wakeAll()設(shè)置一個條件,。wakeOne()隨機(jī)喚醒一個,,wakeAll()喚醒所有。 下面的例子中,,生產(chǎn)者首先必須檢查緩沖是否已滿(numUsedBytes==BufferSize),,如果是,線程停下來等待 bufferNotFull條件,。如果不是,,在緩沖中生產(chǎn)數(shù)據(jù),增加numUsedBytes,激活條件 bufferNotEmpty,。使用mutex來保護(hù)對numUsedBytes的訪問,。另外,QWaitCondition::wait() 接收一個mutex作為參數(shù),,這個mutex應(yīng)該被調(diào)用線程初始化為鎖定狀態(tài),。在線程進(jìn)入休眠狀態(tài)之前,mutex會被解鎖,。而當(dāng)線程被喚醒 時,,mutex會處于鎖定狀態(tài),而且,,從鎖定狀態(tài)到等待狀態(tài)的轉(zhuǎn)換是原子操作,,這阻止了競爭條件的產(chǎn)生。當(dāng)程序開始運(yùn)行時,,只有生產(chǎn)者可以工作,。消費(fèi)者被 阻塞等待bufferNotEmpty條件,一旦生產(chǎn)者在緩沖中放入一個字節(jié),,bufferNotEmpty條件被激發(fā),,消費(fèi)者線程于是被喚醒,。 const int DataSize = 100000; const int BufferSize = 8192; char buffer[BufferSize]; QWaitCondition bufferNotEmpty; QWaitCondition bufferNotFull; QMutex mutex; int numUsedBytes = 0; class Producer : public QThread { public: void run(); }; void Producer::run() { qsrand(QTime(0,0,0).secsTo(QTime::currentTime())); for (int i = 0; i < DataSize; ++i) { mutex.lock(); if (numUsedBytes == BufferSize) bufferNotFull.wait(&mutex); mutex.unlock(); buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4]; mutex.lock(); ++numUsedBytes; bufferNotEmpty.wakeAll(); mutex.unlock(); } } class Consumer : public QThread { public: void run(); }; void Consumer::run() { for (int i = 0; i < DataSize; ++i) { mutex.lock(); if (numUsedBytes == 0) bufferNotEmpty.wait(&mutex); mutex.unlock(); fprintf(stderr, "%c", buffer[i % BufferSize]); mutex.lock(); --numUsedBytes; bufferNotFull.wakeAll(); mutex.unlock(); } fprintf(stderr, "\n"); } int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); Producer producer; Consumer consumer; producer.start(); consumer.start(); producer.wait(); consumer.wait(); return 0; }
|