Qt提供了一个与平台无关的QThread类,用以对线程的支持。多线程编程也可以有效解决在不冻结一个应用程序的用户界面情况下执行一个耗时的操作问题。对应本节的内容,可以在帮助中査看Thread Support in Qt关键字。

这里准备介绍QThread常用函数和启动线程的两种方式:

  • 子类化
  • 使用worker-object通过QObject::moveToThread将它们移动到线程中

一、QThread常用函数

可以将常用的函数按照功能进行以下分类:

  • 线程启动

    • void start()

      调用后会执行run()函数,但在run()函数执行前会发射信号started(),操作系统将根据优先级参数调度线程。如果线程已经在运行,那么这个函数什么也不做。优先级参数的效果取决于操作系统的调度策略。

  • 线程执行

    • int exec()

      每一个线程可以有自己的事件循环,可以通过调用exec()函数来启动事件循环。

    • void run()

      线程的起点,在调用start()之后,新创建的线程就会调用这个函数,默认实现调用exec(),大多数需要重新实现这个函数,便于管理自己的线程。该函数返回后,线程便执行结束,就像应用程序离开main()函数一样。

  • 线程退出

    • void quit()

      使线程退出事件循环,返回0表示成功,相当于调用了QThread::exit(0)。

    • void exit(int returnCode = 0)

      使线程退出事件循环,返回0表示成功,任何非0值表示失败。

    • void terminate()

      在极端情况下,可能想要强制终止一个正在执行的线程,这时可以使用terminate()函数。但是,线程可能会立即被终止也可能不会,这取决于操作系统的调度策略,使用terminate()之后再使用QThread::wait(),以确保万无一失。

      警告:使用terminate()函数,线程可能在任何时刻被终止而无法进行一些淸理工作,因此该函数是很危险的,一般不建议使用,只有在绝对必要的时候使用。

    • void requestInterruption()

      Qt5新引入接口,请求线程的中断,用于关闭线程。该请求是咨询意见并且取决于线程上运行的代码,来决定是否及如何执行这样的请求。此函数不停止线程上运行的任何事件循环,并且在任何情况下都不会终止它。

  • 线程等待

    • void msleep(unsigned long msecs) [static]

      强制当前线程睡眠msecs毫秒。

    • void sleep(unsigned long secs) [static]

      强制当前线程睡眠secs秒。

    • void usleep(unsigned long usecs) [static]

      强制当前线程睡眠usecs微秒。

    • bool wait(unsigned long time = ULONG_MAX)

      线程将会被阻塞,等待time毫秒。和sleep不同的是,如果线程退出,wait会返回。

  • 线程状态

    • bool isFinished() const

      判断线程是否结束

    • bool isRunning() const

      判断线程是否正在运行

    • bool isInterruptionRequested() const

      如果线程上的任务运行应该停止,返回true;可以使用requestInterruption()请求中断。 Qt5新引入接口,用于使长时间运行的任务干净地中断。从不检查或作用于该函数返回值是安全的,但是建议在长时间运行的函数中经常这样做。注意:不要过于频繁调用,以保持较低的开销。示例程序如下:

      void run()
      {
      // 是否请求终止
      while (!isInterruptionRequested())
      {
      // 耗时操作
      }
      }
  • 线程优先级

    • void setPriority(Priority priority)

      设置正在运行线程的优先级。如果线程没有运行,此函数不执行任何操作并立即返回。使用的start()来启动一个线程具有特定的优先级。优先级参数可以是QThread::Priority枚举除InheritPriortyd的任何值。

枚举QThread::Priority:

常量 描述
QThread::IdlePriority 0 没有其它线程运行时才调度
QThread::LowestPriority 1 比LowPriority调度频率低
QThread::LowPriority 2 比NormalPriority调度频率低
QThread::NormalPriority 3 操作系统的默认优先级
QThread::HighPriority 4 比NormalPriority调度频繁
QThread::HighestPriority 5 比HighPriority调度频繁
QThread::TimeCriticalPriority 6 尽可能频繁的调度
QThread::InheritPriority 7 使用和创建线程同样的优先级. 这是默认值

二、子类化QThread方式启动线程

一个QThread代表了一个在应用程序中可以独立控制的线程,它与进程中的其他线程分享数据,但是是独立执行的。相对于一般的程序都是从main()函数开始执行,QThread从main()函数开始执行。默认的,run()通过调用exec()来开启事件循环。要创建一个线程,需要子类化QThread并且重新实现run()函数。例如:

class MyThread : public QThread
{
protected:
void run();
}; void MyThread::run()
{
QTcpSocket socket;
...
socket connectToHost(hostName, portNumber);
exec();
}

这样会在一个线程中创建一个QTcpSocket,然后执行这个线程的事件循环。可以在外部创建该线程的实例,然后调用start()函数来开始执行该线程,start()默认会调用run()函数。当从run()函数返回后,线程便执行结束。

注意,在线程中是无法使用任何的部件类的。

实例程序

下面来看一个在图形界面程序中启动一个线程的例子,在界面上有两个按钮,一个用于开启一个线程,一个用于关闭该线程。新建Qt Gui应用,名称为myThread,类名为Dialog,基类选择QDialog。完成后进入设计模式,向界面中放人两个Push Button按钮,将第一个按钮的显示文本更改为“启动线程”,名称更改为startButton;将第二个按钮的显示文本更改为 “终止线程”,名称更改为stopButton,将其enabled属性取消选中。然后向项目中添加新的C++类,类名设置为“MyThread”,基类设置为“QThread”,类型信息选择“继承自QObject”。完成后进入mythread.h文件,修改如下:

#ifndef MYTHREAD_H
#define MYTHREAD_H #include <QThread> class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = 0);
void stop(); protected:
void run(); private:
volatile bool stopped;
}; #endif // MYTHREAD_H

这里stopped变量使用了volatile关键字,这样可以使它在任何时候都保持最新的值,从而可以避免在多个线程中访问它时出错。然后进入mythread.cpp文件中,修改如下:

#include "mythread.h"
#include <QDebug> MyThread::MyThread(QObject *parent) :
QThread(parent)
{
stopped = false;
} void MyThread::run()
{
qreal i = 0;
while (!stopped)
{
qDebug() << QString("in MyThread: %1").arg(i);
msleep(1000);
i++;
}
stopped = false;
} void MyThread::stop()
{
stopped = true;
}

(1)构造函数中将stopped变量初始化为false。

(2)run()函数中一直判断stopped变量的值,只要它为false,那么每过1秒就一直打印i值递增的字符串。

(3)top()函数中将stopped变量设置为了true,这样便可以结束run()函数中的循环,从而从run()函数中退出,这样整个线程也就结束了。这里使用了stopped变量来实现了进程的终止,并没有使用危险的terminate()函数,也没有在析构函数中使用quit()、wait()和requestInterruption()函数。

下面在Dialog类中使用自定义的线程。先到dialog.h文件中,添加头文件"mythread.h",然后添加私有对象:

#include "mythread.h"
...
private:
MyThread thread;

下面到设计模式,分别进入两个按钮的单击信号对应的槽,更改如下:

// 启动线程按钮
void Dialog::on_startButton_clicked()
{
thread.start();
ui->startButton->setEnabled(false);
ui->stopButton->setEnabled(true);
} // 终止线程按钮
void Dialog::on_stopButton_clicked()
{
if (thread.isRunning())
{
thread.stop();
ui->startButton->setEnabled(true);
ui->stopButton->setEnabled(false);
}
}

启动线程时调用了start()函数,然后设置了两个按钮的状态。在终止线程时, 先使用isRunning()来判断线程是否在运行,如果是,则调用stop()函数来终止线程,并且更改两个按钮的状态。现在运行程序,单击“启动线程”按钮,査看应用程序输出栏的输出,然后再按下“终止线程”按钮,可以看到已经停止输出了。

三、worker-object方式启动线程

还有一种创建线程的方法,就是使用worker-object通过QObject::moveToThread将它们移动到线程中。例如:

//Worker类:在线程中执行的类,例如定时器超时操作
class Worker类 : public QObject
{
Q_OBJECT public slots:
void doWork(const QString &parameter)
{
QString result;
// 这里是阻塞的操作
emit resultReady(result);
} signals:
void resultReady(const QString &result);
}; //Controller类:线程所在的类
class Controller : public QObject
{
Q_OBJECT
QThread workerThread; public:
Controller()
{
Worker *worker = new Worker;
//将worker对象的线程从主线程移动到workerThread
worker->moveToThread(&workerThread);
//当workerThread线程结束后,会自动销毁该线程
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); //当Controller类发射了operate()信号,会调用worker对象的doWork槽函数
connect(this, &Controller::operate, worker, &Worker::doWork);
//当workerr类发射了resultReady()信号,会调用Controller对象的handleResults槽函数
connect(worker, &Worker::resultReady, this, &Controller::handleResults); //最开始,启动workerThread线程
workerThread.start();
} ~Controller()
{
//在析构函数中终止进程
workerThread.requestInterruption();
workerThread.quit();
workerThread.wait();
} public slots:
void handleResults(const QString &); signals:
void operate(const QString &);
};

这样Worker槽中的代码将在一个单独的线程中执行,使用这种方式可以很容易地将一些费时的操作放到单独的工作线程中来完成。可以将任意线程中任意对象的任意一个信号关联到Worker的槽上,不同线程间的信号和槽进行关联是安全的。

注:另外有时间可以参考Qt——线程与定时器这篇博客进一步了解启动线程的这两种方式。

四、关闭线程

关闭线程有多种方法,这里介绍一种最常用的方法:在析构函数中使用quit()、wait()和requestInterruption()函数。示例程序如下:

~WorkerThread()
{
// 请求终止
requestInterruption();
quit();
wait();
}

参考:

Qt 之 QThread

Qt 进程和线程之二:启动线程的更多相关文章

  1. Java基础-进程与线程之Thread类详解

    Java基础-进程与线程之Thread类详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.进程与线程的区别 简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程 ...

  2. 多线程之:java线程创建

    java中创建线程有两种方式: 1.继承Thread类,重写run()方法,如: public class MyThread extends Thread { public void run(){ S ...

  3. C# 多线程之List的线程安全问题

    网上关于List的线程安全问题将的很少,所以自己实验了一把,发现确实是线程不安全的.所以当你在进行多线程编程中使用了共享的List集合,必须对其进行线程安全处理. List的Add方法是线程不安全的, ...

  4. Java多线程之Thread、Runnable、Callable及线程池

    一.多线程 线程是指进程中的一个执行流程,一个进程中可以有多个线程.如java.exe进程中可以运行很多线程.进程是运行中的程序,是内存等资源的集合,线程是属于某个进程的,进程中的多个线程共享进程中的 ...

  5. 多线程之Java线程阻塞与唤醒

    线程的阻塞和唤醒在多线程并发过程中是一个关键点,当线程数量达到很大的数量级时,并发可能带来很多隐蔽的问题.如何正确暂停一个线程,暂停后又如何在一个要求的时间点恢复,这些都需要仔细考虑的细节.在Java ...

  6. Qt 进程和线程之三:线程同步、可重入与线程安全

    一.同步线程方法 使用线程的目的是允许代码并行运行,但是有时线程必须停止并等待其他线程.例如,如果两个线程试图同时写入相同的变量,结果是不确定的,所以需要同步线程.同步线程是一种保护共享资源等数据的常 ...

  7. 进程线程之pid,tid

    Linux中,每个进程有一个pid,类型pid_t,由getpid()取得.Linux下的POSIX线程也有一个id,类型pthread_t,由pthread_self()取得,该id由线程维护,其i ...

  8. JAVA多线程之UncaughtExceptionHandler——处理非正常的线程中止

    JAVA多线程之UncaughtExceptionHandler——处理非正常的线程中止 背景 当单线程的程序发生一个未捕获的异常时我们可以采用try....catch进行异常的捕获,但是在多线程环境 ...

  9. 并发和多线程(二)--启动和中断线程(Interrupt)的正确姿势

    启动线程: 从一个最基本的面试题开始,启动线程到底是start()还是run()? Runnable runnable = () -> System.out.println(Thread.cur ...

随机推荐

  1. Xcode使用的一些小技巧,值得一看。

    有时我们需要对一个已有项目进行重构,改进设计,提高代码质量.以下几个Xcode 4中的功能,会使重构的工作变得轻松很多. 1.打开项目我的项目是Xcode3.x中编辑的,在用Xcode 4 打开时出现 ...

  2. 修改mysql的root的密码

    首先用root账号登陆phpmyadmin,然后点击左侧的mysql,然后选择进入mysql数据库,输入以下命令:update user set password=password('123456') ...

  3. extjs grid renderer参数用法

    今天在导出EXT的二维时老是报错,追进去看是renderer : function(value)的参数不对,经过一番研究,未免以后遇到再次浪费时间,记录一下. var cm = new Ext.gri ...

  4. ffmpeg视频格式转换中关键帧的设置

    在用ffmpeg转换视频到flv过程中,需要设置关键帧的间隔,以便在播放过程中实现精确定位.在网上查找了不少,最后发现这个指令有效: -g 1 -keyint_min 2 . http://blog. ...

  5. android布局中使用include及需注意点

    在android布局中,使用include,将另一个xml文件引入,可作为布局的一部分,但在使用include时,需注意以下问题: 一.使用include引入 如现有标题栏布局block_header ...

  6. css中块元素和行内元素区别

    行内元素特点 1.和其他元素都在一行上: 2.元素的高度.宽度.行高及顶部和底部边距不可设置: 3.元素的宽度就是它包含的文字或图片的宽度,不可改变. 块元素特点 1.每个块级元素都从新的一行开始,并 ...

  7. linux 查看某进程 并杀死进程 ps grep kill

    Linux 中使用top 或 ps 查看进程使用kill杀死进程 1.使用top查看进程: $top 进行执行如上命令即可查看top!但是难点在如何以进程的cpu占用量进行排序呢? cpu占用量排序执 ...

  8. bzoj 2151 种树——贪心+后悔

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2151 似乎是半年+前讲过的.(然而看到的时候却不会了) 考虑贪心,限制就是不能选两边的.如果 ...

  9. 2.row_number() over (partition by col1 order by col2)的用法

    row_number() over (partition by col1 order by col2) 表示根据COL1分组,在分组内部根据 COL2排序,而此函数计算的值就表示每组内部排序后的顺序编 ...

  10. [hdu2243]考研路茫茫——单词情结(AC自动机+矩阵快速幂)

    题意:长度不超过L,只由小写字母组成的,至少包含一个词根的单词,一共可能有多少个. 解题关键:利用补集转化的思想,先求一个词根也不包含的单词个数,然后用总的减去即可.长度不超过L需要用矩阵维数增加一倍 ...