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. HDU5965 扫雷 —— dp递推

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5965 题解: 1. 用a[]数组记录第二行的数字,用dp[]记录没一列放的地雷数.如果第一列的地雷数d ...

  2. Program received signal SIGSEGV, Segmentation fault.

    GDB调试的时候出现了: Program received signal SIGSEGV, Segmentation fault.(程序收到信号SIGSEGV,分段故障) SIGSEGV:在POSIX ...

  3. H264视频通过RTMP直播

    http://blog.csdn.net/firehood_/article/details/8783589 前面的文章中提到了通过RTSP(Real Time Streaming Protocol) ...

  4. 在Eclipse Java EE编译器中修改Web项目的发布名称

    在工程目录上右键, 选properties, 弹出属性窗口, 选中Web Project Settings, 在右边的Context root中修改保存即可 死马当做活马医 在你的工程目录下找到.se ...

  5. SSL peer shut down incorrectly

    这个问题通常出现在Android Studio更新失败的时候, 原因是download http://services.gradle.org/distributions/gradle-2.2-all. ...

  6. org.jetbrains.android.uipreview.RenderingException: Failed to load the LayoutLib: com/android/layoutlib/bridge/Bridge : Unsupported major.minor version 52.0

    在Android Studio使用的时候,突然发现Preview功能不能用了,报了一个错,错误如下 org.jetbrains.android.uipreview.RenderingException ...

  7. 详解linux中install命令和cp命令的区别

    基本上,在Makefile里会用到install,其他地方会用cp命令. 它们完成同样的任务——拷贝文件,它们之间的区别主要如下: .最重要的一点,如果目标文件存在,cp会先清空文件后往里写入新文件, ...

  8. [CTSC 2018] 混合果汁

    [题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=5343 [算法] 对于每组询问 , 首先二分答案 显然 , 最优策略为优先选择价格低的 ...

  9. some characters cannot be mapped using iso-8859-1 character encoding

    Eclipse中新建一个.properties文件,如果输入中文保存时就会提示错误 Reason:some characters cannot be mapped using "ISO-88 ...

  10. AutoIT:界面与自动化操作结合来简化日常劳动: .Net Reactor验证License,设置License,创建License,截图AutoIt自动化实现。(七)

    版本六中存在一个显著问题是: 当exe文件生存之后,运行的时候,通过consoleWrite函数打印出来的数据是无法展示出来的.这就存在一个问题:当运行失败的时候,我还是看不到任何log信息. 于是, ...