1. 程式人生 > >Qt多執行緒程式設計總結(一)(所有GUI物件都是執行緒不安全的)

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

相關推薦

Qt執行程式設計總結所有GUI物件執行安全

Qt對執行緒提供了支援,基本形式有獨立於平臺的執行緒類、執行緒安全方式的事件傳遞和一個全域性Qt庫互斥量允許你可以從不同的執行緒呼叫Qt方法。 這個文件是提供給那些對多執行緒程式設計有豐富的知識和經驗的聽眾的。推薦閱讀: 警告:所有的GUI類(比如,QWidget和它的子類),作業系統核心類(比如,QPr

Qt執行程式設計總結

Qt對執行緒提供了支援,基本形式有獨立於平臺的執行緒類、執行緒安全方式的事件傳遞和一個全域性Qt庫互斥量允許你可以從不同的執行緒呼叫Qt方法。 這個文件是提供給那些對多執行緒程式設計有豐富的知識和經驗的聽眾的。推薦閱讀: 警告:所有的GUI類(比如,QWidget和它的

java執行程式設計總結()

為了深入學習java多執行緒程式設計,我閱讀了高洪巖老師的《Java多執行緒程式設計核心技術》這本書,不得不說,這本書的內容實在到位,這是一本值得推薦閱讀的書,我在閱讀完電子版後還特意去圖書館找到了這本書的紙質版,於是開始了java多執行緒程式設計的學習,期間將

執行程式設計總結——條件變數和互斥鎖

#include <stdio.h> #include <pthread.h> #include <error.h> #include <assert.h> #include <stdlib.h> typedef int DataType; typ

Java執行程式設計核心技術()Java執行技能

主題:     這套文章主要是把書上的內容自己消化一遍,把核心內容跟重點挑出來。讀者可以用最少的時間瞭解這本書的內容。        第一章主要講解多執行緒的概念,使用方法,一些基本的api;每小節的內容不超過十句話的總結如下:   層級結構上是:     1.

Java執行程式設計總結筆記——03概念與原理

作業系統中執行緒和程序的概念 現在的作業系統是多工作業系統。多執行緒是實現多工的一種方式。 程序是指一個記憶體中執行的應用程式,每個程序都有自己獨立的一塊記憶體空間,一個程序中可以啟動多個執行緒。比如在Windows系統中,一個執行的exe就是一個程序。執行

Java執行程式設計總結筆記——02執行基礎知識

讀解Thread類API 構造方法摘要 Thread(Runnable target) 分配新的 Thread 物件。 Thread(String name) 分配新的 Thread 物件。 方法摘要 static Thread cur

Java執行程式設計總結筆記——01 Java語言的執行

GUI應用程式 幾乎所有的GUI應用程式都會用多執行緒。舉例來說加入現在有人在用word編輯一個比較大的文字檔案剛剛才做過單字“查詢”操作,當word進行查詢時,螢幕上會出現“停止查詢按鈕”,使用者可以隨時停止查詢。這個功能其實就用到了多執行緒。 (1)執行

Java執行知識點總結——進階篇 之 等待喚醒機制 Lock 鎖升級版

JDK1.5 中提供了多執行緒升級解決方案。 將同步 Synchronized 替換成現實 Lock 操作。 將Object中的 wait、notify、notifyAll,替換成了C

Linux C 執行程式設計總結

執行緒的資料處理   和程序相比,執行緒的最大優點之一是資料的共享性,各個程序共享父程序處沿襲的資料段,可以方便的獲得、修改資料。但這也給多執行緒程式設計帶來了許多問題。我們必須當心有多個不同的程序訪問相同的變數。許多函式是不可重入的,即同時不能執行一個函式的多個拷貝(除非使用不同的資料段)。在函式中宣告的

JAVA併發:執行程式設計之同步“監視器monitor”

在JAVA虛擬機器中,每個物件(Object和class)通過某種邏輯關聯監視器,為了實現監視器的互斥功能,每個物件(Object和class)都關聯著一個鎖(有時也叫“互斥量”),這個鎖在作業系統書籍中稱為“訊號量”,互斥(“mutex”)是一個二進位制的訊號量。 如果一個執行緒擁有了某些資料的鎖,其他的

【iOS沉思錄】NSThread、GCD、NSOperation執行程式設計總結

OC中的多執行緒 OC中多執行緒根據封裝程度可以分為三個層次:NSThread、GCD和NSOperation,另外由於OC相容C語言,因此仍然可以使用C語言的POSIX介面來實現多執行緒,只需引入相應的標頭檔案:#include <pthrea

Java執行知識點總結——進階篇執行下的單例模式

餓漢式 餓漢式多執行緒和單執行緒的程式碼是一樣的,如下: class Single { private static final Single s = new Single(); p

MFC執行程式設計總結

        在MFC程式中使用AfxBeginThread函式來建立一個執行緒,該函式因引數不同而具有兩種過載函式,分別對應工作者執行緒和使用者介面(UI)執行緒。 一、工作執行緒 1、建立執行

執行程式設計 基礎篇 ()

基礎篇 (一)[寫在前面]    隨著計算機技術的發展,程式設計模型也越來越複雜多樣化.但多執行緒程式設計模型是目前計算機系統架構的最終模型.隨著CPU主頻的不斷攀升,X86架構的硬體已經成為瓶,在這種架構的CPU主頻最高為4G.事實上目前3.6G主頻的CPU已經接近了頂峰.

執行程式設計學習十(ThreadPoolExecutor 詳解).

一、ThreadPoolExecutor 引數說明 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,

Android程序Process開發總結-優點與缺陷-個人註釋版,非絕對原創

1、背景 我公司產品,一共有三個程序,其中主程序一個、子程序一個、推送程序一個   Androiod多程序 為何使用多程序,有啥好處?  推送業務為何都要獨立程序,這裡涉及到的一個知識就是程序保活技術,推送程序只要不掛掉,那麼推送保證沒有問題 a、不會

程式設計俱樂部每日2018年12月7日QAQ的小遊戲

程式設計俱樂部每日一練(2018年12月7日)QAQ的小遊戲 Description Recently,QAQ fell in love a small game,which simulates browser browsing web pages.It has three kind

程式設計俱樂部每日2018年12月3日A * B Problem大數乘法

程式設計俱樂部每日一練(2018年12月3日)A * B Problem大數乘法 A * B Problem Description Now Give you two integers A and B , please caculate the value of A multiply

程式設計俱樂部每日2018年12月2日 A - B problem大數減法

程式設計俱樂部每日一練(2018年12月2日) A - B problem大數減法 Description Now, Give you two intgers A and B , Please calculate the value of A minus B. Attation: