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
#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~"); }
#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的值,但实际情况是...
因为我在点击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
#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); }
mainwindow中添加一个ComputeThread对象和一个槽。
private: ComputeThread *computePiThread; private slots: void slotShowResult(double result);
#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作为框架来学习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
#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); }
这里还涉及到数据的访问。最开始的例子用的是信号槽的方式进行访问,而这里直接使用的指针进行访问。
渲染结果:一个不断旋转的正方形,(假装看见了...)
QtConcurrent
提供了一些高级API,使得写多线程程序可以不再使用像互斥、读写锁、等待条件、信号量等低级的多线程命令。用QtConcurrent写的程序可以根据内核数量自动调整线程数。这意味着今天写的应用程序将来可以部署在多核系统上。
尽管如此,QtConcurrent 不能用于线程运行时需要通信的情况,而且它也不应该被用来处理阻塞操作。
QReadWriteLock
是一个读写锁,主要用来同步保护需要读写的资源。当你想多个读线程可以同时读取资源,但是只能有一个写线程操作资源,而其他线程必须等待写线程完成时,这时候用这个读写锁就很有用了。可以实现多个读,一个写,读之间可以不同步不互斥,写时会阻塞其他的写操作。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
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。