溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

深入理解Qt多線程

發布時間:2020-06-24 14:34:10 來源:網絡 閱讀:377 作者:拳四郎 欄目:開發技術

提要

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

         每個程序啟動后就會擁有一個線程。該線程稱為”主線程”(在Qt應用程序中也叫”GUI線程”)。Qt GUI必須運行在此線程上。所有的圖形元件和幾個相關的類,如QPixmap,不能工作于非主線程中。非主線程通常稱為”工作者線程”,因為它主要處理從主線程中卸下的一些工作。

         有時候,你需要的不僅僅是在另一線程的上下文中運行一個函數。您可能需要有一個生存在另一個線程中的對象來為 GUI線程提供服務。也許你想在另一個始終運行的線程中來輪詢硬件端口并在有關注的事情發生時發送信號到GUI線程。Qt為開發多線程應用程序提供了多種 不同的解決方案。解決方案的選擇依賴于新線程的目的以及線程的生命周期。


環境

Ubuntu 12.04 64bit

Qt 4.8.1


一個簡單的例子

首先來看一個單線程的例子。

用Qt Creator創建一個Qt Gui工程,只有一個mainwindow類,代碼如下:

mainwindow.h

#ifndef MAINWINDOW_H #define MAINWINDOW_H  #include <QtGui/QMainWindow> #include <QPushButton> #include <QLabel> #include <QHBoxLayout>  class MainWindow : public QMainWindow {     Q_OBJECT private:     QPushButton *calButton;     QPushButton *hiButton;     QLabel *mLabel;  public:     MainWindow(QWidget *parent = 0);     ~MainWindow();  private slots:     void slotGetPi();     void slotSayHi();  };  #endif // MAINWINDOW_H 

mainwindow.cpp

#include "mainwindow.h"  MainWindow::MainWindow(QWidget *parent)     : QMainWindow(parent) {     QHBoxLayout *mainLayout=new QHBoxLayout();      calButton = new QPushButton(this);     calButton->setText("GetPi");      hiButton = new QPushButton(this);     hiButton->setText("Hi");      mLabel = new QLabel();     mLabel->setText("Bitch");      mainLayout->setSpacing(10);     mainLayout->addWidget(calButton);     mainLayout->addWidget(hiButton);     mainLayout->addWidget(mLabel);      QWidget *centreWidget=new QWidget(this);     centreWidget->setLayout(mainLayout);     this->setCentralWidget(centreWidget);      this->connect(calButton,SIGNAL(released()),this, SLOT(slotGetPi()));     this->connect(hiButton,SIGNAL(released()),this, SLOT(slotSayHi())); }  MainWindow::~MainWindow() {      } void MainWindow::slotGetPi()  {     int time = 1000000000;     float result=0;     for(int i=1;i<=time;i++)     {         double value=4.0/(2*i-1);         if (i % 2 == 1) result+=value;         else result-=value;     }     mLabel->setText(QString::number(result)); }  void MainWindow::slotSayHi() {     mLabel->setText("Hei,gay~"); } 

main.cpp

#include <QtGui/QApplication> #include "mainwindow.h"  int main(int argc, char *argv[]) {     QApplication a(argc, argv);     MainWindow w;     w.show();          return a.exec(); } 

代碼很簡單,就是一個窗口中有兩個Button和一個label, 一個Button計算Pi,一個Button "say hi" 兩個button都可以更新label.

預記的運行效果是點擊button之后就可以改變label的值,但實際情況是...

深入理解Qt多線程


因為我在點擊GetPi這個Button的時候,程序就開始計算,當然是在主線程中,這時候整個界面就阻塞了,Hi Button 設置關閉窗口操作都無法完成,這時就不得不用線程了。


用線程改寫一下。

創建一個ComputeThread類,繼承自QThread。

computethread.h

#ifndef COMPUTETHREAD_H #define COMPUTETHREAD_H  #include <QThread> #include <QDebug> #include <computethread.h>  class ComputeThread : public QThread {     Q_OBJECT private:     void run(); public:     explicit ComputeThread(QObject *parent = 0);      signals:      void computeFinish(double result); public slots:      };  #endif // COMPUTETHREAD_H 

computethread.cpp

#include "computethread.h"  ComputeThread::ComputeThread(QObject *parent) :     QThread(parent) { }   void ComputeThread::run() {     qDebug()<<this->currentThreadId()<<":Begin computing!"<<endl;     int time = 1000000000;     float result=0;     for(int i=1;i<=time;i++)     {         double value=4.0/(2*i-1);         if (i % 2 == 1) result+=value;         else result-=value;     }     emit this->computeFinish(result); } 

在run中定義線程運行的內容,還定義了一個信號,在計算完畢的時候將結果發射出去。


mainwindow中添加一個ComputeThread對象和一個槽。

private:     ComputeThread *computePiThread;  private slots:     void slotShowResult(double result);

cpp中加入線程操作的部分:

#include "mainwindow.h"  MainWindow::MainWindow(QWidget *parent)     : QMainWindow(parent) {     QVBoxLayout *mainLayout=new QVBoxLayout();      calButton = new QPushButton(this);     calButton->setText("GetPi");      hiButton = new QPushButton(this);     hiButton->setText("Hi");      mLabel = new QLabel();     mLabel->setText("Bitch");      computePiThread = new ComputeThread;      mainLayout->setSpacing(10);     mainLayout->addWidget(calButton);     mainLayout->addWidget(hiButton);     mainLayout->addWidget(mLabel);      QWidget *centreWidget=new QWidget(this);     centreWidget->setLayout(mainLayout);     this->setCentralWidget(centreWidget);      this->connect(computePiThread,SIGNAL(computeFinish(double)),this, SLOT(slotShowResult(double)));     this->connect(hiButton,SIGNAL(released()),this, SLOT(slotSayHi()));     this->connect(calButton,SIGNAL(released()),this, SLOT(slotGetPi()));  }  MainWindow::~MainWindow() {     computePiThread->terminate();     computePiThread->wait();     delete computePiThread;     computePiThread = 0; } void MainWindow::slotGetPi()  {     computePiThread->start(); }   void MainWindow::slotSayHi() {     mLabel->setText("Hei,gay~"); }  void MainWindow::slotShowResult(double result) {     mLabel->setText(QString::number(result)); } 

運行結果

深入理解Qt多線程


修改之后計算就在子線程中進行,主線程就沒有卡死的情況了。


MultiThread in Opengl

之前有用QT作為框架來學習OpenGL,參考這里。

當是有個問題沒有解決,就是當想要GLWidget中的圖形不斷的進行變換的話,就要在主線程中加一個死循環,這樣做只是權宜之記,最好的解決方法就是用多線程。

創建一個GLThread類專門用來渲染:

glthread.h

#ifndef GLTHREAD_H #define GLTHREAD_H  #include <QThread> #include <QSize> #include <QTime> #include<GL/glu.h>  class GLWidget;  class GLThread : public QThread { public:     GLThread(GLWidget *glWidget);     void resizeViewport(const QSize &size);     void run();     void stop();  private:     bool doRendering;     bool doResize;     int w;     int h;     GLWidget *glw; };  #endif // GLTHREAD_H 

glthread.cpp

#include "glthread.h" #include "glwidget.h"  GLThread::GLThread(GLWidget *gl)     : QThread(), glw(gl) {     doRendering = true;     doResize = false; }  void GLThread::stop() {     doRendering = false; }  void GLThread::resizeViewport(const QSize &size) {     w = size.width();     h = size.height();     doResize = true; }  void GLThread::run() {      glw->makeCurrent();     this->rotAngle = 0.0;     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);		// This Will Clear The Background Color To Black     glClearDepth(1.0);				// Enables Clearing Of The Depth Buffer     glDepthFunc(GL_LESS);				// The Type Of Depth Test To Do     glEnable(GL_DEPTH_TEST);			// Enables Depth Testing     glShadeModel(GL_SMOOTH);			// Enables Smooth Color Shading      glMatrixMode(GL_PROJECTION);     glLoadIdentity();				// Reset The Projection Matrix      gluPerspective(45.0f,(GLfloat)w/(GLfloat)h,0.1f,100.0f);	// Calculate The Aspect Ratio Of The Window      glMatrixMode(GL_MODELVIEW);      while (doRendering)     {         rotAngle +=5;         if(rotAngle>=360)             rotAngle = 0;         if (doResize)         {             glViewport(0, 0, w, h);             doResize = false;              glMatrixMode(GL_PROJECTION);             glLoadIdentity();              gluPerspective(45.0f,(GLfloat)w/(GLfloat)h,0.1f,100.0f);             glMatrixMode(GL_MODELVIEW);         }         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear The Screen And The Depth Buffer         glLoadIdentity();				// Reset The View          glTranslatef(-1.5f,0.0f,-6.0f);		// Move Left 1.5 Units And Into The Screen 6.0          glRotatef(rotAngle,0.0f,0.0f,1.0f);		// Rotate The Triangle On The Y axis         // draw a triangle (in smooth coloring mode)         glBegin(GL_POLYGON);				// start drawing a polygon         glColor3f(1.0f,0.0f,0.0f);			// Set The Color To Red         glVertex3f( 0.0f, 1.0f, 0.0f);		// Top         glColor3f(0.0f,1.0f,0.0f);			// Set The Color To Green         glVertex3f( 1.0f,-1.0f, 0.0f);		// Bottom Right         glColor3f(0.0f,0.0f,1.0f);			// Set The Color To Blue         glVertex3f(-1.0f,-1.0f, 0.0f);		// Bottom Left         glEnd();					// we're done with the polygon (smooth color interpolation)         glw->swapBuffers();         msleep(50);         qDebug("rendering");     } } 


GLWidget也要進行相應的修改:

glwidget.h

#ifndef GLWIDGET_H #define GLWIDGET_H  #include <QGLWidget> #include "glthread.h" #include <QResizeEvent>  class GLWidget : public QGLWidget { public:     GLWidget(QWidget *parent);     void startRendering();     void stopRendering();  protected:     void resizeEvent(QResizeEvent *evt);     void paintEvent(QPaintEvent *);     void closeEvent(QCloseEvent *evt);      GLThread glt; };  #endif // GLWIDGET_H 

glwidget.cpp

#include "glwidget.h"  GLWidget::GLWidget(QWidget *parent)        : glt(this)    {        setAutoBufferSwap(false);        resize(320, 240);    }     void GLWidget::startRendering()    {         glt.start();    }     void GLWidget::stopRendering()    {        glt.stop();        glt.wait();    }     void GLWidget::resizeEvent(QResizeEvent *evt)    {        glt.resizeViewport(evt->size());    }     void GLWidget::paintEvent(QPaintEvent *)    {        // Handled by the GLThread.    }     void GLWidget::closeEvent(QCloseEvent *evt)    {        stopRendering();        QGLWidget::closeEvent(evt);    } 

注意這里用到了C++的一個小技巧,前向聲明。當兩個類要互相引用的時候不能夠互相包含頭文件,在一個類的頭文件中,必須用Class + 類名作為前向聲明。而在這個類的cpp中要訪問另一個類的具體方法的話,必須包含那個類的頭文件。

這里還涉及到數據的訪問。最開始的例子用的是信號槽的方式進行訪問,而這里直接使用的指針進行訪問。


渲染結果:一個不斷旋轉的正方形,(假裝看見了...)

深入理解Qt多線程


線程安全

       如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。
或者說:一個類或者程序所提供的接口對于線程來說是原子操作或者多個線程之間的切換不會導致該接口的執行結果存在二義性,也就是說我們不用考慮同步的問題。
線程安全問題都是由全局變量及靜態變量引起的。

        若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。

一些瑣碎

QtConcurrent

       提供了一些高級API,使得寫多線程程序可以不再使用像互斥、讀寫鎖、等待條件、信號量等低級的多線程命令。用QtConcurrent寫的程序可以根據內核數量自動調整線程數。這意味著今天寫的應用程序將來可以部署在多核系統上。

盡管如此,QtConcurrent 不能用于線程運行時需要通信的情況,而且它也不應該被用來處理阻塞操作。

QReadWriteLock

       是一個讀寫鎖,主要用來同步保護需要讀寫的資源。當你想多個讀線程可以同時讀取資源,但是只能有一個寫線程操作資源,而其他線程必須等待寫線程完成時,這時候用這個讀寫鎖就很有用了??梢詫崿F多個讀,一個寫,讀之間可以不同步不互斥,寫時會阻塞其他的寫操作。QReadWriteLock也有遞歸和非遞歸模式之分。

用法

 QReadWriteLock lock;   void ReaderThread::run()  {      lock.lockForRead();      read_file();      lock.unlock(); }   void WriterThread::run()  {      lock.lockForWrite();      write_file();      lock.unlock();  }



參考

解析Qt中QThread使用方法 - http://mobile.51cto.com/symbian-268690_all.htm


Glimpsing the Third Dimension - http://doc.qt.digia.com/qq/qq06-glimpsing.html#writingmultithreadedglapplications

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女