Qt 进程和线程:启动线程的两种方式
liebian365 2024-10-17 14:00 18 浏览 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 - 博客园
相关推荐
- 4万多吨豪华游轮遇险 竟是因为这个原因……
-
(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...
- “菜鸟黑客”必用兵器之“渗透测试篇二”
-
"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...
- 科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白
-
作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...
- 麦子陪你做作业(二):KEGG通路数据库的正确打开姿势
-
作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...
- 知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势
-
智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...
- 每日新闻播报(September 14)_每日新闻播报英文
-
AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...
- 香港新巴城巴开放实时到站数据 供科技界研发使用
-
中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...
- 5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper
-
本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...
- Qt动画效果展示_qt显示图片
-
今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...
- 如何从0到1设计实现一门自己的脚本语言
-
作者:dong...
- 三年级语文上册 仿写句子 需要的直接下载打印吧
-
描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...
- C++|那些一看就很简洁、优雅、经典的小代码段
-
目录0等概率随机洗牌:1大小写转换2字符串复制...
- 二年级上册语文必考句子仿写,家长打印,孩子照着练
-
二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 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)