程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> 關於C >> 深入理解Qt多線程

深入理解Qt多線程

編輯:關於C

提要

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 
#include 
#include 
#include 

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 
#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的值,但實際情況是...

/

 

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

 

用線程改寫一下。

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

computethread.h

 

#ifndef COMPUTETHREAD_H
#define COMPUTETHREAD_H

#include 
#include 
#include 

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()<currentThreadId()<<:Begin computing!<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));
}

運行結果

 

/

 

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

 

MultiThread in Opengl

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

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

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

glthread.h

 

#ifndef GLTHREAD_H
#define GLTHREAD_H

#include 
#include 
#include 
#include

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 
#include glthread.h
#include 

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中要訪問另一個類的具體方法的話,必須包含那個類的頭文件。

 

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

 

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

/

 

線程安全

待研究

一些瑣碎

待研究

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved