Qt 进程和线程:启动线程的两种方式
liebian365 2024-10-17 14:00 24 浏览 0 评论
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 ?meter)
{
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();
}
文章转自博客园(fengMisaka):Qt 进程和线程之二:启动线程的两种方式 - fengMisaka - 博客园
相关推荐
- 深度解密epoll 如何工作的?(epoll基本处理流程)
-
epoll...
- 大乐透第19082期:头奖开出7注1000万分落六地 奖池41亿元
-
2019年7月17日晚开奖的体彩超级大乐透第19082期开奖号码为:前区06、18、20、21、31,后区03、04。本期大乐透前区号码五区比为1:0:3:0:1,二区和四区号码没有给出。当期前区和值...
- 【开奖】4月27日周六:福彩、体彩(2021年4月27日体彩开奖结果)
-
4月27日开奖福彩3D第2019110期:61222选5第2019110期:0812202122排列3第19110期:303排列5第19110期:30305大乐透第19047期:0304...
- “红狒狒”落户哈尔滨铁路局(哈尔滨铁路红肠)
-
这几天,“红人”“红狒狒”在牡丹江机务段可引起了不小的轰动,众粉丝争相与其拍照留念,在该段人气爆棚!“红狒狒”到底何许人也?“红狒狒”,中文名:和谐3D型电力机车;绰号:红狒狒、番茄;制造商:大连机...
- 2D、3D、2.5D,做游戏还是搞噱头?玩家都晕了
-
前言游戏类型就像某种潮流,一种流行罢,另一种接棒成为主流。前两年的新作大多以“开放世界”为标签,在追求纯沙盒的过程中打造出一些细致的分类,比如说“类GTA沙盒”。诚然,纯碎的沙盒游戏并不多见,业内只有...
- 《战神4》PC版宣传片发布 GTX 1070即可60帧畅玩
-
在今年10月的时候索尼PlayStation官方正式宣布圣莫尼卡2018年的《战神4》将于2022年1月14日推出PC版本,官方在今天公布了一段PC版宣传片,并且公开了游戏的配置需求。下面让我们一起来...
- 男星深情好丈夫形象崩塌,半夜搂美女坐大腿,举止亲密
-
近日,于晓光被拍到深夜在酒吧玩,结束后与一名女子一起上车离开。上车后,女子直接坐在了他腿上,他也顺势搂着美女,美女满脸笑容地坐在他腿上玩手机离开。可能有人会好奇,于晓光是谁呢?于晓光是韩国艺人秋瓷炫的...
- d3d12dll丢失怎么修复?d3d12dll加载失败怎么解决?
-
d3d12.dll丢失怎么修复?d3d12.dll加载失败怎么解决?很多朋友想要运行游戏的时候都会遇到这个问题,这种情况该怎么办呢?今天系统之家小编给朋友们讲讲具体的解决方法,操作其实还蛮简单的。...
- 许多玩家反馈《生化4RE》PC一直崩溃 无法进入游戏
-
今日(3月24日),卡普空《生化危机4:重制版》正式发售,然而有部分PC玩家遇到了游戏崩溃等问题。很多玩家在贴吧发帖称游戏遇到了严重的崩溃问题,且经常反复,报错代码普遍为FatalD3Derror...
- 微软正式推出适用于WSL Linux的D3D12 GPU视频加速技术
-
今天,微软正式向WindowsSubsystemforLinux(WSL)用户发布了Direct3D12GPU视频加速支持。在微软通过WSL允许在Linux下使用Open...
- 《怪物猎人:崛起》曙光系统报错“Fatal d3d error”的解决办法
-
《怪物猎人:崛起》曙光系统报错“Fatald3derror”的解决办法不少小伙伴反应《怪物猎人:崛起》DLC曙光预载以后打不开游戏,出现了Fatald3derror类似的错误代码,这类问题的解...
- Mac+双屏,前端程序员的专业配置 - Loctek 乐歌 D3D 双屏电脑显示器支架
-
做FE也有一段日子了,电脑屏幕每天在设计稿、浏览器、IDE、即时通讯工具、Terminal、邮箱之间切换。虽然mac的工作区带来了很多灵活,但是依然略显不足。于是入手支架,把公司配的电脑和显示器发挥起...
- RPC 的原理和简单使用(rpc详解)
-
RPC的概念RPC,RemoteProcedureCall,翻译成中文就是远程过程调用,是一种进程间通信方式。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数。在调用的...
- 大厂开源的golang微服务rpc框架 — kitex
-
提前rpc估计所有的开发同学都知道,不知道的也无所谓,毕竟我也好几年没用了,今天带大家在复习一下。RPC(RemoteProcedureCall):远程过程调用,...
- 干货!一文掌握Protobuf所有语言所有用法,快收藏
-
说实话,Protobuf这个库,让人相见时难别亦难,东风无力百花残,每次等到要用它的时候,总感觉还没有完全掌握它的用法,而实际上等去百度或者谷歌的时候,教程都是多么的凌乱不堪。学会它,最直接关系到的,...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)