Qt多線程編程總結(一)(所有GUI對象都是線程不安全的)


Qt對線程提供了支持,基本形式有獨立於平台的線程類、線程安全方式的事件傳遞和一個全局Qt庫互斥量允許你可以從不同的線程調用Qt方法。

這個文檔是提供給那些對多線程編程有豐富的知識和經驗的聽眾的。推薦閱讀:

警告:所有的GUI類(比如,QWidget和它的子類),操作系統核心類(比如,QProcess)和網絡類都是線程安全的。

QRegExp使用一個靜態緩存並且也不是線程安全的,即使通過使用QMutex來保護的QRegExp對象。

線程類

最重要的類是QThread,也就是說要開始一個新的線程,就是開始執行你重新實現的QThread::run()。這和Java的線程類很相似。

為了寫線程程序,在兩個線程同時希望訪問同一個數據時,對數據進行保護是很必要的。因此這里也有一個QMutex類,一個線程可以鎖定互斥量,並且在它鎖定之后,其它線程就不能再鎖定這個互斥量了,試圖這樣做的線程都會被阻塞直到互斥量被釋放。例如:

    class MyClass
    {
    public:
        void doStuff( int );
    	private:
        QMutex mutex;
        int a;
        int b;
    };

    // 這里設置a為c,b為c*2。

    void MyClass::doStuff( int c )
    {
        mutex.lock();
	 a = c;
	 b = c * 2;
	mutex.unlock();
    } 

這保證了同一時間只有一個線程可以進入MyClass::doStuff(),所以b將永遠等於c * 2

另外一個線程也需要在一個給定的條件下等待其它線程的喚醒,QWaitCondition類就被提供了。線程等待的條件QWaitCondition指出發生了什么事情,阻塞將一直持續到這種事情發生。當某種事情發生了,QWaitCondition可以喚醒等待這一事件的線程之一或全部。(這和POSIX線程條件變量是具有相同功能的並且它也是Unix上的一種實現。)例如:

    #include <qapplication.h>
    #include <qpushbutton.h>
    // 全局條件變量
    QWaitCondition mycond;
    // Worker類實現
  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()));
            // 調用從QThread繼承來的start()方法……這將立即開始線程的執行
            QThread::start();
        }
	public slots:
        void slotClicked()
        {    // 喚醒等待這個條件變量的一個線程
            mycond.wakeOne();
        }
	protected:
        void run()
        {
            // 這個方法將被新創建的線程調用……
            while ( TRUE ) {
                // 鎖定應用程序互斥鎖,並且設置窗口標題來表明我們正在等待開始工作
                qApp->lock();
                setCaption( "Waiting" );
                qApp->unlock();
                // 等待直到我們被告知可以繼續
                mycond.wait();
                // 如果我們到了這里,我們已經被另一個線程喚醒……讓我們來設置標題來表明我們正在工作
                qApp->lock();
                setCaption( "Working!" );
                qApp->unlock();
                // 這可能會占用一些時間,幾秒、幾分鍾或者幾小時等等,因為這個一個和GUI線程分開的線程,在處理事件時,GUI線程不會停下來……
                do_complicated_thing();
            }
        }
    };
	// 主線程——所有的GUI事件都由這個線程處理。
    int main( int argc, char **argv )
    {
        QApplication app( argc, argv );
        // 創建一個worker……當我們這樣做的時候,這個worker將在一個線程中運行
        Worker firstworker( 0, "worker" );
        app.setMainWidget( &worker );
        worker.show();
        return app.exec();
    }
  

只要你按下按鈕,這個程序就會喚醒worker線程,這個線程將會進行並且做一些工作並且然后會回來繼續等待被告知做更多的工作。如果當按鈕被按下時,worker線程正在工作,那么就什么也不會發生。當線程完成了工作並且再次調用QWaitCondition::wait(),然后它就會被開始。

線程安全的事件傳遞

在Qt中,一個線程總是一個事件線程——確實是這樣的,線程從窗口系統中拉出事件並且把它們分發給窗口部件。靜態方法QThread::postEvent從線程中傳遞事件,而不同於事件線程。事件線程被喚醒並且事件就像一個普通窗口系統事件那樣在事件線程中被分發。例如,你可以強制一個窗口部件通過如下這樣做的一個不同的線程來進行重繪:

    QWidget *mywidget;
    QThread::postEvent( mywidget, new QPaintEvent( QRect(0, 0, 100, 100) ) );
  

這(異步地)將使mywidget重繪一塊100*100的正方形區域。

Qt庫互斥量

Qt庫互斥量提供了從線程而不是事件線程中調用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中沒有使用互斥量而調用一個函數通常情況下結果將是不可預知的。從另外一個線程中調用Qt的一個GUI相關函數需要使用Qt庫互斥量。在這種情況下,所有可能最終訪問任何圖形或者窗口系統資源的都是GUI相關的。使用容器類,字符串或者輸入/輸出類,如果對象只被一個線程使用就不需要任何互斥量了。

告誡

當進行線程編程時,需要注意的一些事情:

  • 當使用Qt庫互斥量的時候不要做任何阻塞操作。這將會凍結事件循環。
  • 確認你鎖定一個遞歸QMutex的次數和解鎖的次數一樣,不能多也不能少。
  • 在調用除了Qt容器和工具類的任何東西之前鎖定Qt應用程序互斥量。
  • 謹防隱含地共享類,你應該避免在線程之間使用操作符=()來復制它們。這將會在Qt的未來主要的或次要的發行版本中進行改進。
  • 謹防那些沒有被設計為線程安全的Qt類,例如,QPtrList的應用程序接口就不是線程安全的並且如果不同的線程需要遍歷一個QPtrList,它們應該在調用QPtrList::first()之前鎖定並且在到達終點之后解鎖,而不是在QPtrList::next()的前后進行鎖定和解鎖。
  • 確認只在GUI線程中創建的繼承和使用了QWidget、QTimer和QSocketNotifier的對象。在一些平台上,在某個不是GUI線程的線程中創建這樣的對象將永遠不會接受到底層窗口系統的事件。
  • 和上面很相似,只在GUI線程中使用QNetwork類。一個經常被問到的問題是一個QSocket是否可以在多線程中使用。這不是必須得,因為所有的QNetwork類都是異步的。
  • 不要在不是GUI線程的線程中試圖調用processEvents()函數。這也包括QDialog::exec()、QPopupMenu::exec()、QApplication::processEvents()和其它一些。
  • 在你的應用程序中,不要把普通的Qt庫和支持線程的Qt庫混合使用。這也就是說如果你的程序使用了支持線程的Qt庫,你就不應該連接普通的Qt庫、動態的載入普通Qt庫或者動態地連接其它依賴普通Qt庫的庫或者插件。在一些系統上,這樣做會導致Qt庫中使用的靜態數據變得不可靠了。


QT通過三種形式提供了對線程的支持。它們分別是,一、平台無關的線程類,二、線程安全的事件投遞,三、跨線程的信號-槽連接。這使得開發輕巧的多線程 Qt程序更為容易,並能充分利用多處理器機器的優勢。多線程編程也是一個有用的模式,它用於解決執行較長時間的操作而不至於用戶界面失去響應。
Qt 線程類
Qt 包含下面一些線程相關的類:
QThread 提供了開始一個新線程的方法
QThreadStorage 提供逐線程數據存儲
QMutex 提供相互排斥的鎖,或互斥量
QMutexLocker 是一個便利類,它可以自動對QMutex加鎖與解鎖
QReadWriteLock 提供了一個可以同時讀寫操作的鎖
QReadLocker與QWriteLocker 是便利類,它自動對QReadWriteLock加鎖與解鎖
QSemaphore 提供了一個整型信號量,是互斥量的泛化
QWaitCondition 提供了一種方法,使得線程可以在被另外線程喚醒之前一直休眠。

Qt 高級線程類
QtConcurrent 開啟線程事務
QFutureWatcher 觀測線程狀態
QFuture 線程啟動類
QThread創建線程
為創建一個線程,子類化QThread並且重寫它的run()函數,例如:
class MyThread : public QThread
 {
     Q_OBJECT
 protected:
     void run();
 };
 void MyThread::run()
 {
     ...
 }

之后調用start,Qt即可創建一個線程,並在線程中執行run()函數中代碼,注意UI非線程安全的。

QtConcurrent創建線程
QtConcurrent 創建線程的方法比較多, 而且QtConcurrent 本身比較特殊,若系統有空閑線程時,它會調度空閑線程,無空閑線程時將會創建一個線程。
(注意:QtConcurrent 創建線程歸QthreadPool管理,若超過最大線程數,將會進入隊列等待),QtConcurrent創建線程的方法多種,以下舉例map函數:
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 提供了線程同步的手段。使用線程的主要想法是希望它們可以盡可能並發執行,而一些關鍵點上線程之間需要停止或等待。例如,假如兩個線程試圖同時訪問同一個 全局變量,結果可能不如所願。

QMutex
QMutex 提供相互排斥的鎖,或互斥量。在一個時刻至多一個線程擁有mutex,假如一個線程試圖訪問已經被鎖定的mutex,那么它將休眠,直到擁有mutex的線程對此mutex解鎖。Mutexes常用來保護共享數據訪問。
QReadWriterLock
QReadWriterLock 與QMutex相似,除了它對 “read”,”write”訪問進行區別對待。它使得多個讀者可以共時訪問數據。使用QReadWriteLock而不是QMutex,可以使得多線程程序更具有並發性。
 QReadWriteLock lock;
 void ReaderThread::run()
 {
     lock.lockForRead();
     read_file();
     lock.unlock();
 }
 void WriterThread::run()
 {
     lock.lockForWrite();
     write_file();
     lock.unlock();
 }

QSemaphore
QSemaphore 是QMutex的一般化,它可以保護一定數量的相同資源,與此相對,一個mutex只保護一個資源。下面例子中,使用QSemaphore來控制對環狀緩 沖的訪問,此緩沖區被生產者線程和消費者線程共享。生產者不斷向緩沖寫入數據直到緩沖末端,再從頭開始。消費者從緩沖不斷讀取數據。信號量比互斥量有更好 的並發性,假如我們用互斥量來控制對緩沖的訪問,那么生產者,消費者不能同時訪問緩沖。然而,我們知道在同一時刻,不同線程訪問緩沖的不同部分並沒有什么 危害。
 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 允許線程在某些情況發生時喚醒另外的線程。一個或多個線程可以阻塞等待一QWaitCondition ,用wakeOne()或wakeAll()設置一個條件。wakeOne()隨機喚醒一個,wakeAll()喚醒所有。
下面的例子中,生產者首先必須檢查緩沖是否已滿(numUsedBytes==BufferSize),如果是,線程停下來等待 bufferNotFull條件。如果不是,在緩沖中生產數據,增加numUsedBytes,激活條件 bufferNotEmpty。使用mutex來保護對numUsedBytes的訪問。另外,QWaitCondition::wait() 接收一個mutex作為參數,這個mutex應該被調用線程初始化為鎖定狀態。在線程進入休眠狀態之前,mutex會被解鎖。而當線程被喚醒 時,mutex會處於鎖定狀態,而且,從鎖定狀態到等待狀態的轉換是原子操作,這阻止了競爭條件的產生。當程序開始運行時,只有生產者可以工作。消費者被 阻塞等待bufferNotEmpty條件,一旦生產者在緩沖中放入一個字節,bufferNotEmpty條件被激發,消費者線程於是被喚醒。
 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;
 }

http://blog.csdn.net/emdfans/article/details/41745007


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM