Qt 进程和线程之二:启动线程

Qt提供了对线程的支持,这包括一组与平台无关的线程类、一个线程安全的发送事件的方式,以及跨线程的信号槽的关联。这些使得可以很容易地开发可移植的多线程Qt应用程序,可以充分利用多处理器的机器。多线程编程也可以有效解决在不冻结一个应用程序的用户界面情况下执行一个耗时的操作问题。对应本节的内容,可以在帮助中査看Thread Support in Qt关键字。

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

  • 子类化QThread
  • Worker-Object

一、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()

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

  • 线程等待
    • 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

      判断线程是否正在运行

  • 线程优先级
    • 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方式启动线程

Qt中的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()函数返回后,线程便执行结束。

QThread类详解

(3)每一个线程可以有自己的事件循环,可以通过调用exec()函数来启动事件循环, 可以通过调用exit()或者quit()来停止事件循环。在一个线程中拥有一个事件循环, 可以使它能够关联其他线程中的信号到本线程的槽上,这使用了队列关联机制,就是在使用connect()函数进行信号和梢的关联时,将Qt::ConnectionType类型的参数指定为Qt::QueuedConnection。拥有事件循环还可以使该线程能够使用需要事件循环的类,比如QTimer和QTcpSocket类等。注意,在线程中是无法使用任何的部件类的。

(5)静态函数currentThreadld()和currentThread()可以返回当前执行的线程的标识符,前者返回一个该线程的系统特定的ID;后者返回一个QThread指针。QThread也提供了多个平台无关的睡眠函数,其中sleep()精度为秒,msleep()精度为毫秒,usleep()精度为微秒。

实例程序

下面来看一个在图形界面程序中启动一个线程的例子,在界面上有两个按钮,一个用于开启一个线程,一个用于关闭该线程。新建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()函数。

下面在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将它们移动到线程中。例如:

class Worker : public QObject
{
    Q_OBJECT

public slots:
    void doWork(const QString &parameter)
    {
        QString result;
        // 这里是昂贵的或阻塞的操作
        emit resultReady(result);
    }

signals:
    void resultReady(const QString &result);
};

class Controller : public QObject
{
    Q_OBJECT
    QThread workerThread;

public:
    Controller()
    {
        Worker *worker = new Worker;
        worker->moveToThread(&workerThread);
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
        connect(this, &Controller::operate, worker, &Worker::doWork);
        connect(worker, &Worker::resultReady, this, &Controller::handleResults);
        workerThread.start();
    }

    ~Controller()
    {
        workerThread.quit();
        workerThread.wait();
    }

public slots:
    void handleResults(const QString &);

signals:
    void operate(const QString &);
};

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

参考:

Qt 之 QThread

原文地址:https://www.cnblogs.com/linuxAndMcu/p/11076305.html

时间: 2024-11-13 06:39:18

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

java多线程之Executor框架线程池详细介绍与ThreadPoolExecutor

Executor框架简介 Executor框架的结构 Executor框架主要由3大部分组成: 任务: 包括被执行的任务需要实现的接口:Runable 接口.Callable接口: 任务的执行: 包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口.Executor框架有两个关键类实现了ExecutorService接口:ThreadPoolExecutor 和 ScheduledThreadPoolExecutor.ForkJoinPool

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

线程的阻塞和唤醒在多线程并发过程中是一个关键点,当线程数量达到很大的数量级时,并发可能带来很多隐蔽的问题.如何正确暂停一个线程,暂停后又如何在一个要求的时间点恢复,这些都需要仔细考虑的细节.在Java发展史上曾经使用suspend().resume()方法对于线程进行阻塞唤醒,但随之出现很多问题,比较典型的还是死锁问题.如下代码,主要的逻辑代码是主线程启动线程mt一段时间后尝试使用suspend()让线程挂起,最后使用resume()恢复线程.但现实并不如愿,执行到suspend()时将一直卡住

多线程基础二(线程的启动、终止,线程面临的三种问题)

一.线程的启动.终止方式 启动: start native(调用外部接口启动) 终止:    stop(类似kill,暴力终止)  interrupt 中断的方式 通过指令的方式 volatile boolean stop = false; public class InterruptDemo { private static int i; public static void main(String[] args) { Thread thread = new Thread(()->{ whil

线程八大基础核心二(启动线程)

1.引子 在java多线程并发编程中,有八大基础核心.考考你:看看都有哪八大基础核心呢?它们分别是: 1.创建线程的方式 2.线程启动 3.线程停止 4.线程生命周期 5.线程相关的方法 6.线程相关的属性 7.线程异常处理 8.线程安全 今天我们从第二个基础核心开始:启动线程 2.考考你 #前情回顾: 1.在java编程语言中,创建好线程对象后,通过调用start方法,启动线程 Thread t1 = new Thread(); t1.start(); #考考你: 1.问题一:可以调用两次st

Java多线程之wait(),notify(),notifyAll()

在多线程的情况下,因为同一进程的多个线程共享同一片存储空间,在带来方便的同一时候,也带来了訪问冲突这个严重的问题.Java语言提供了专门机制以解决这样的冲突,有效避免了同一个数据对象被多个线程同一时候訪问. wait与notify是java同步机制中重要的组成部分.结合与synchronizedkeyword使用,能够建立非常多优秀的同步模型. synchronized(this){ }等价于publicsynchronized void method(){.....} 同步分为类级别和对象级别

JAVA多线程之volatile 与 synchronized 的比较

一,volatile关键字的可见性 要想理解volatile关键字,得先了解下JAVA的内存模型,Java内存模型的抽象示意图如下: 从图中可以看出: ①每个线程都有一个自己的本地内存空间--线程栈空间???线程执行时,先把变量从主内存读取到线程自己的本地内存空间,然后再对该变量进行操作 ②对该变量操作完后,在某个时间再把变量刷新回主内存 关于JAVA内存模型,更详细的可参考: 深入理解Java内存模型(一)——基础 因此,就存在内存可见性问题,看一个示例程序:(摘自书上) 1 public c

JavaSE学习52:细说多线程之Thread类和Runable接口

一线程创建的两种方式比较 线程创建和启动有两种方式,这里只是列出步骤,不再进行详细解释. (1)继承Thread类 class MyThread extends Thread{ public void run(){ ... } } MyThread mt=new MyThread();//创建线程 mt.start();//启动线程 (2)实现Runnable接口 class MyThread implements Runnable{ public void run(){ ... } } MyT

Android-——多线程之Handler(转)

Android--多线程之Handler 原文地址:http://www.cnblogs.com/shirley-1019/p/3557800.html 前言 Android的消息传递机制是另外一种形式的“事件处理”,这种机制主要是为了解决Android应用中多线程的问题,在Android中不 允许Activity新启动的线程访问该Activity里的UI组件,这样会导致新启动的线程无法改变UI组件的属性值.但实际开发中,很多地方需要在 工作线程中改变UI组件的属性值,比如下载网络图片.动画等等

Android 多线程之IntentService 完全详解

关联文章: Android 多线程之HandlerThread 完全详解 Android 多线程之IntentService 完全详解 android多线程-AsyncTask之工作原理深入解析(上) android多线程-AsyncTask之工作原理深入解析(下) IntentService 一.IntentService概述 ??上一篇我们聊到了HandlerThread,本篇我们就来看看HandlerThread在IntentService中的应用,看本篇前建议先看看上篇的HandlerT