溫馨提示×

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

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

深入理解Qt多線程

發(fā)布時(shí)間:2020-06-24 14:34:10 來(lái)源:網(wǎng)絡(luò) 閱讀:357 作者:拳四郎 欄目:開(kāi)發(fā)技術(shù)

提要

        Qt對(duì)線程提供了支持,基本形式有獨(dú)立于平臺(tái)的線程類(lèi)、線程安全方式的事件傳遞和一個(gè)全局Qt庫(kù)互斥量允許你可以從不同的線程調(diào)用Qt方法。

         每個(gè)程序啟動(dòng)后就會(huì)擁有一個(gè)線程。該線程稱(chēng)為”主線程”(在Qt應(yīng)用程序中也叫”GUI線程”)。Qt GUI必須運(yùn)行在此線程上。所有的圖形元件和幾個(gè)相關(guān)的類(lèi),如QPixmap,不能工作于非主線程中。非主線程通常稱(chēng)為”工作者線程”,因?yàn)樗饕幚韽闹骶€程中卸下的一些工作。

         有時(shí)候,你需要的不僅僅是在另一線程的上下文中運(yùn)行一個(gè)函數(shù)。您可能需要有一個(gè)生存在另一個(gè)線程中的對(duì)象來(lái)為 GUI線程提供服務(wù)。也許你想在另一個(gè)始終運(yùn)行的線程中來(lái)輪詢(xún)硬件端口并在有關(guān)注的事情發(fā)生時(shí)發(fā)送信號(hào)到GUI線程。Qt為開(kāi)發(fā)多線程應(yīng)用程序提供了多種 不同的解決方案。解決方案的選擇依賴(lài)于新線程的目的以及線程的生命周期。


環(huán)境

Ubuntu 12.04 64bit

Qt 4.8.1


一個(gè)簡(jiǎn)單的例子

首先來(lái)看一個(gè)單線程的例子。

用Qt Creator創(chuàng)建一個(gè)Qt Gui工程,只有一個(gè)mainwindow類(lèi),代碼如下:

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(); } 

代碼很簡(jiǎn)單,就是一個(gè)窗口中有兩個(gè)Button和一個(gè)label, 一個(gè)Button計(jì)算Pi,一個(gè)Button "say hi" 兩個(gè)button都可以更新label.

預(yù)記的運(yùn)行效果是點(diǎn)擊button之后就可以改變label的值,但實(shí)際情況是...

深入理解Qt多線程


因?yàn)槲以邳c(diǎn)擊GetPi這個(gè)Button的時(shí)候,程序就開(kāi)始計(jì)算,當(dāng)然是在主線程中,這時(shí)候整個(gè)界面就阻塞了,Hi Button 設(shè)置關(guān)閉窗口操作都無(wú)法完成,這時(shí)就不得不用線程了。


用線程改寫(xiě)一下。

創(chuàng)建一個(gè)ComputeThread類(lèi),繼承自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中定義線程運(yùn)行的內(nèi)容,還定義了一個(gè)信號(hào),在計(jì)算完畢的時(shí)候?qū)⒔Y(jié)果發(fā)射出去。


mainwindow中添加一個(gè)ComputeThread對(duì)象和一個(gè)槽。

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)); } 

運(yùn)行結(jié)果

深入理解Qt多線程


修改之后計(jì)算就在子線程中進(jìn)行,主線程就沒(méi)有卡死的情況了。


MultiThread in Opengl

之前有用QT作為框架來(lái)學(xué)習(xí)OpenGL,參考這里。

當(dāng)是有個(gè)問(wèn)題沒(méi)有解決,就是當(dāng)想要GLWidget中的圖形不斷的進(jìn)行變換的話,就要在主線程中加一個(gè)死循環(huán),這樣做只是權(quán)宜之記,最好的解決方法就是用多線程。

創(chuàng)建一個(gè)GLThread類(lèi)專(zhuān)門(mén)用來(lái)渲染:

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也要進(jìn)行相應(yīng)的修改:

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++的一個(gè)小技巧,前向聲明。當(dāng)兩個(gè)類(lèi)要互相引用的時(shí)候不能夠互相包含頭文件,在一個(gè)類(lèi)的頭文件中,必須用Class + 類(lèi)名作為前向聲明。而在這個(gè)類(lèi)的cpp中要訪問(wèn)另一個(gè)類(lèi)的具體方法的話,必須包含那個(gè)類(lèi)的頭文件。

這里還涉及到數(shù)據(jù)的訪問(wèn)。最開(kāi)始的例子用的是信號(hào)槽的方式進(jìn)行訪問(wèn),而這里直接使用的指針進(jìn)行訪問(wèn)。


渲染結(jié)果:一個(gè)不斷旋轉(zhuǎn)的正方形,(假裝看見(jiàn)了...)

深入理解Qt多線程


線程安全

       如果你的代碼所在的進(jìn)程中有多個(gè)線程在同時(shí)運(yùn)行,而這些線程可能會(huì)同時(shí)運(yùn)行這段代碼。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的。
或者說(shuō):一個(gè)類(lèi)或者程序所提供的接口對(duì)于線程來(lái)說(shuō)是原子操作或者多個(gè)線程之間的切換不會(huì)導(dǎo)致該接口的執(zhí)行結(jié)果存在二義性,也就是說(shuō)我們不用考慮同步的問(wèn)題。
線程安全問(wèn)題都是由全局變量及靜態(tài)變量引起的。

        若每個(gè)線程中對(duì)全局變量、靜態(tài)變量只有讀操作,而無(wú)寫(xiě)操作,一般來(lái)說(shuō),這個(gè)全局變量是線程安全的;若有多個(gè)線程同時(shí)執(zhí)行寫(xiě)操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。

一些瑣碎

QtConcurrent

       提供了一些高級(jí)API,使得寫(xiě)多線程程序可以不再使用像互斥、讀寫(xiě)鎖、等待條件、信號(hào)量等低級(jí)的多線程命令。用QtConcurrent寫(xiě)的程序可以根據(jù)內(nèi)核數(shù)量自動(dòng)調(diào)整線程數(shù)。這意味著今天寫(xiě)的應(yīng)用程序?qū)?lái)可以部署在多核系統(tǒng)上。

盡管如此,QtConcurrent 不能用于線程運(yùn)行時(shí)需要通信的情況,而且它也不應(yīng)該被用來(lái)處理阻塞操作。

QReadWriteLock

       是一個(gè)讀寫(xiě)鎖,主要用來(lái)同步保護(hù)需要讀寫(xiě)的資源。當(dāng)你想多個(gè)讀線程可以同時(shí)讀取資源,但是只能有一個(gè)寫(xiě)線程操作資源,而其他線程必須等待寫(xiě)線程完成時(shí),這時(shí)候用這個(gè)讀寫(xiě)鎖就很有用了??梢詫?shí)現(xiàn)多個(gè)讀,一個(gè)寫(xiě),讀之間可以不同步不互斥,寫(xiě)時(shí)會(huì)阻塞其他的寫(xiě)操作。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問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI