提要
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的值,但实际情况是...
因为我在点击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)); }
运行结果
修改之后计算就在子线程中进行,主线程就没有卡死的情况了。
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中QThread使用方法 - http://mobile.51cto.com/symbian-268690_all.htm
Glimpsing the Third Dimension - http://doc.qt.digia.com/qq/qq06-glimpsing.html#writingmultithreadedglapplications