13-1_Qt 5.9 C++开发指南_多线程及QThread 创建多线程程序_ThreadSignal

一个应用程序一般只有一个线程,一个线程内的操作是顺序执行的,如果有某个比较消耗时间的计算或操作,比如网络通信中的文件传输,在一个线程内操作时,用户界面就可能会冻结而不能及时响应。这种情况下,可以创建一个单独的线程来执行比较消耗时间的操作,并与主线程之间处理好同步与数据交互,这就是多线程应用程序

Qt 为多线程操作提供了完整的支持。QThread 是线程类,是实现多线程操作的核心类,一般从QThread 继承定义自己的线程类。

线程之间的同步是其交互的主要问题,Qt 提供了 QMutex、QMutexLocker、QReadWriteLock、QwaitCondition、QSemaphore 等多种类用于实现线程之间的同步

Qt 还有 Qt Concurrent 模块,提供一些高级的 API 实现多线程编程而无需使用 QMutex、QwaitCondition 和QSemaphore 等基础操作。使用Qt Concurrent 实现的多线程程序可以自动根据处理器内核个数调整线程个数。

文章目录

  • 1. QThread 类功能简介
  • 2. 掷骰子的线程QDiceThread
  • 3. 掷骰子的多线程应用程序
    • 3.1 可视化UI设计
    • 3.2 代码分析
    • 3.3 程序结构及源码
      • 3.3.1 程序结构
      • 3.3.2 qdicethread.h
      • 3.3.3 qdicethread.cpp
      • 3.3.4 dialog.h
      • 3.3.5 dialog.cpp

1. QThread 类功能简介

QThread会起一个子线程,并可以通过信号槽将变量传递到主线程中。

QThread 类提供不依赖于平台的管理线程的方法。一个 QThread 类的对象管理一个线程,一般从QThread 继承一个自定义类,并重定义虚函数run(),在run()函数里实现线程需要完成的任务。

将应用程序的线程称为主线程,额外创建的线程称为工作线程。一般在主线程里创建工作线程,并调用 start()开始执行工作线程的任务。start()会在内部调用 run()函数,进入工作线程的事件循环,在 run()函数里调用 exit()或 quit()可以结束线程的事件循环,或在主线程里调用 terminate()强制结束线程。

QThread 类的主要接口函数、信号和槽函数见下表。

在这里插入图片描述
在这里插入图片描述
QThread 是 QObject 的子类,所以可以使用信号与槽机制。QThread 自身定义了 started()和finished()两个信号,started()信号在线程开始执行之前发射,也就是在 run()函数被调用之前,finished0信号在线程就要结束时发射。

在进行本章的学习前,建议先复习C++新特性中对应部分,Qt这里就是对C++11的语法进行封装。

2. 掷骰子的线程QDiceThread

作为实例,定义一个掷骰子的线程类QDiceThread,类的声明部分如下:

#ifndef QDICETHREAD_H
#define QDICETHREAD_H#include    <QThread>class QDiceThread : public QThread
{Q_OBJECT
private:int     m_seq=0;//掷骰子次数序号int     m_diceValue;//骰子点数bool    m_Paused=true; //掷一次骰子bool    m_stop=false; //停止线程
protected:void    run() Q_DECL_OVERRIDE;  //线程任务
public:QDiceThread();void    diceBegin();//掷一次骰子void    dicePause();//暂停void    stopThread(); //结束线程
signals:void    newValue(int seq,int diceValue); //产生新点数的信号
};#endif // QDICETHREAD_H

重载虚函数 run(),在此函数里完成线程的主要任务。

自定义 diceBegin()、dicePause()、stopThread()3 个公共函数用于线程控制,这3 个函数由主线程调用。

定义了一个信号 newValue(int seq,int diceValue) 用于在掷一次子得到新的点数之后发射此信号,由主线程的槽函数响应以获取值。
QDiceThread 类的实现代码如下:

#include "qdicethread.h"
#include    <QTime>QDiceThread::QDiceThread()
{}void QDiceThread::diceBegin()
{ //开始掷骰子m_Paused=false;
}void QDiceThread::dicePause()
{//暂停掷骰子m_Paused=true;
}void QDiceThread::stopThread()
{//停止线程m_stop=true;
}void QDiceThread::run()
{//线程任务m_stop=false;//启动线程时令m_stop=falsem_seq=0; //掷骰子次数qsrand(QTime::currentTime().msec());//随机数初始化,qsrand是线程安全的while(!m_stop)//循环主体{if (!m_Paused){m_diceValue=qrand(); //获取随机数m_diceValue=(m_diceValue % 6)+1;m_seq++;emit newValue(m_seq,m_diceValue);  //发射信号}msleep(500); //线程休眠500ms}//  在  m_stop==true时结束线程任务quit();//相当于  exit(0),退出线程的事件循环
}

其中,run()是线程任务的实现部分,线程开始就执行 run()函数。run()函数一般是事件循环过程,根据各种条件或事件处理各种任务。当run()函数退出时,线程的事件循环就结束了。

在run()函数里,初始化变量 m_stop 和m_seq,用qsrand()函数对随机数种子初始化。run()函数的主循环体是一个 while循环,在主线程调用 stopThread()函数使 m_stop 为 true,才会退出 while循环,调用quit()之后结束线程。

在 while 循环体内,又根据 m_Paused 判断当前是否需要掷子,如果需要掷骰子,则用随机函数生成一次子的点数 m_diceValue,然后发射信号 newValue(),将 m seq和m diceValue作为信号参数传递出去。主线程可以设计槽函数与此信号关联,获取这两个值并进行显示。

3. 掷骰子的多线程应用程序

使用QDiceThread 类,设计一个应用程序 samp13_1,程序运行界面如下图所示。
在这里插入图片描述
窗体上方的几个按钮用于控制线程的启动与停止,控制开始与暂停掷骰子。中间的文本框显示次数和点数,右边根据点数显示资源文件里面的一个图片,图片存储在项目的资源文件里。下方的一个标签根据QDiceThread 的 started()和finished()两个信号显示线程的状态。

3.1 可视化UI设计

在这里插入图片描述

3.2 代码分析

窗口类是从QDialog 继承的类 Dialog,其类定义如下(省略了按钮槽函数的定义):

#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>#include    "qdicethread.h"namespace Ui {
class Dialog;
}class Dialog : public QDialog
{Q_OBJECTprivate:QDiceThread   threadA;protected:void    closeEvent(QCloseEvent *event);public:explicit Dialog(QWidget *parent = 0);~Dialog();private slots:
...private:Ui::Dialog *ui;
};#endif // DIALOG_H

这里定义了一个QDiceThread 类型的变量 threadA,重定义了 closeEvent()事件,自定义了3个槽函数。
Dialog类的构造函数代码如下:

Dialog::Dialog(QWidget *parent) : QDialog(parent),  ui(new Ui::Dialog)
{//构造函数ui->setupUi(this);connect(&threadA,SIGNAL(started()),this,SLOT(onthreadA_started()));connect(&threadA,SIGNAL(finished()),this,SLOT(onthreadA_finished()));connect(&threadA,SIGNAL(newValue(int,int)),this,SLOT(onthreadA_newValue(int,int)));
}

构造函数主要是将 threadA 的 3 个信号与 Dialog 自定义的3 个槽函数相关联,这3 个槽函数的代码如下:

void Dialog::onthreadA_started()
{//线程的started()信号的响应槽函数ui->LabA->setText("Thread状态:thread started");
}void Dialog::onthreadA_finished()
{//线程的 finished()信号的响应槽函数ui->LabA->setText("Thread状态:thread finished");
}void Dialog::onthreadA_newValue(int seq,int diceValue)
{//QDiceThread的newValue()信号的响应槽函数,显示骰子次数和点数QString  str=QString::asprintf("第 %d 次掷骰子,点数为:%d",seq,diceValue);ui->plainTextEdit->appendPlainText(str);QPixmap pic; //图片显示QString filename=QString::asprintf(":/dice/images/d%d.jpg",diceValue);pic.load(filename);ui->LabPic->setPixmap(pic);
}

started()信号发射时,表示线程开始执行,在标签里显示状态文字。

finished()信号发射时,表示线程结束执行,在标签里显示状态文字。

newValue()是 QDiceThread 定义的信号,在掷一次骰子获得新的点数后发射,将掷假子的次数和点数传递过来。槽函数onthreadA_newValue()获取这两个值并显示在文本框里,再根据点数从资源文件里获取相应的图片并显示。

窗口上5个按钮的代码如下:

void Dialog::on_btnStartThread_clicked()
{//启动线程 按钮threadA.start();ui->btnStartThread->setEnabled(false);ui->btnStopThread->setEnabled(true);ui->btnDiceBegin->setEnabled(true);ui->btnDiceEnd->setEnabled(false);
}void Dialog::on_btnDiceBegin_clicked()
{//开始 掷骰子按钮threadA.diceBegin();ui->btnDiceBegin->setEnabled(false);ui->btnDiceEnd->setEnabled(true);
}void Dialog::on_btnDiceEnd_clicked()
{//暂停 掷骰子按钮threadA.dicePause();ui->btnDiceBegin->setEnabled(true);ui->btnDiceEnd->setEnabled(false);
}void Dialog::on_btnStopThread_clicked()
{//结束线程 按钮threadA.stopThread();//结束线程的run()函数执行threadA.wait();//ui->btnStartThread->setEnabled(true);ui->btnStopThread->setEnabled(false);ui->btnDiceBegin->setEnabled(false);ui->btnDiceEnd->setEnabled(false);
}void Dialog::on_btnClear_clicked()
{ //清空文本 按钮ui->plainTextEdit->clear();
}

“启动线程”按钮调用线程的 start()函数,start()函数会内部调用 run()函数开始线程任务的执行。run()函数将内部变量 m_Paused 初始化为true,所以,启动线程后并不会立即开始掷散子。

“开始”按钮调用 diceBegin()函数,使 threadA 线程内部变量 m_Paused 变为 false,那么run()函数里就开始每隔 500 毫秒产生一次骰子点数,并发射信号 newValue()。
“暂停”按钮调用 dicePause()函数,使 threadA 线程内部变量 m_Paused 变为 true,run()函数里不再掷骰子,但是 run()函数并没有结束,也就是线程并没有结束。
“结束线程”按钮调用 stopThread()函数,使threadA 线程内部的 m_stop 变为 true,run()函数体的 while 循环结束,执行 quit()后线程结束。所以,线程结束就是 run()函数执行退出。

重载closeEvent()事件,在窗口关闭时确保线程被停止,代码如下:

void Dialog::closeEvent(QCloseEvent *event)
{ //窗口关闭事件,必须结束线程if (threadA.isRunning()){threadA.stopThread();threadA.wait();}event->accept();
}

3.3 程序结构及源码

3.3.1 程序结构

在这里插入图片描述

3.3.2 qdicethread.h

#ifndef QDICETHREAD_H
#define QDICETHREAD_H#include    <QThread>class QDiceThread : public QThread
{Q_OBJECT
private:int     m_seq=0;//掷骰子次数序号int     m_diceValue;//骰子点数bool    m_Paused=true; //掷一次骰子bool    m_stop=false; //停止线程
protected:void    run() Q_DECL_OVERRIDE;  //线程任务
public:QDiceThread();void    diceBegin();//掷一次骰子void    dicePause();//暂停void    stopThread(); //结束线程
signals:void    newValue(int seq,int diceValue); //产生新点数的信号
};#endif // QDICETHREAD_H

3.3.3 qdicethread.cpp

#include "qdicethread.h"
#include    <QTime>QDiceThread::QDiceThread()
{}void QDiceThread::diceBegin()
{ //开始掷骰子m_Paused=false;
}void QDiceThread::dicePause()
{//暂停掷骰子m_Paused=true;
}void QDiceThread::stopThread()
{//停止线程m_stop=true;
}void QDiceThread::run()
{//线程任务m_stop=false;//启动线程时令m_stop=falsem_seq=0; //掷骰子次数qsrand(QTime::currentTime().msec());//随机数初始化,qsrand是线程安全的while(!m_stop)//循环主体{if (!m_Paused){m_diceValue=qrand(); //获取随机数m_diceValue=(m_diceValue % 6)+1;m_seq++;emit newValue(m_seq,m_diceValue);  //发射信号}msleep(500); //线程休眠500ms}//  在  m_stop==true时结束线程任务quit();//相当于  exit(0),退出线程的事件循环
}

3.3.4 dialog.h

#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>#include    "qdicethread.h"namespace Ui {
class Dialog;
}class Dialog : public QDialog
{Q_OBJECTprivate:QDiceThread   threadA;protected:void    closeEvent(QCloseEvent *event);public:explicit Dialog(QWidget *parent = 0);~Dialog();private slots:void    onthreadA_started();void    onthreadA_finished();void    onthreadA_newValue(int seq, int diceValue);void on_btnClear_clicked();void on_btnDiceEnd_clicked();void on_btnDiceBegin_clicked();void on_btnStopThread_clicked();void on_btnStartThread_clicked();private:Ui::Dialog *ui;
};#endif // DIALOG_H

3.3.5 dialog.cpp

#include "dialog.h"
#include "ui_dialog.h"void Dialog::closeEvent(QCloseEvent *event)
{ //窗口关闭事件,必须结束线程if (threadA.isRunning()){threadA.stopThread();threadA.wait();}event->accept();
}Dialog::Dialog(QWidget *parent) : QDialog(parent),  ui(new Ui::Dialog)
{//构造函数ui->setupUi(this);connect(&threadA,SIGNAL(started()),this,SLOT(onthreadA_started()));connect(&threadA,SIGNAL(finished()),this,SLOT(onthreadA_finished()));connect(&threadA,SIGNAL(newValue(int,int)),this,SLOT(onthreadA_newValue(int,int)));
}Dialog::~Dialog()
{delete ui;
}void Dialog::onthreadA_started()
{//线程的started()信号的响应槽函数ui->LabA->setText("Thread状态:thread started");
}void Dialog::onthreadA_finished()
{//线程的 finished()信号的响应槽函数ui->LabA->setText("Thread状态:thread finished");
}void Dialog::onthreadA_newValue(int seq,int diceValue)
{//QDiceThread的newValue()信号的响应槽函数,显示骰子次数和点数QString  str=QString::asprintf("第 %d 次掷骰子,点数为:%d",seq,diceValue);ui->plainTextEdit->appendPlainText(str);QPixmap pic; //图片显示QString filename=QString::asprintf(":/dice/images/d%d.jpg",diceValue);pic.load(filename);ui->LabPic->setPixmap(pic);
}void Dialog::on_btnClear_clicked()
{ //清空文本 按钮ui->plainTextEdit->clear();
}void Dialog::on_btnDiceEnd_clicked()
{//暂停 掷骰子按钮threadA.dicePause();ui->btnDiceBegin->setEnabled(true);ui->btnDiceEnd->setEnabled(false);
}void Dialog::on_btnDiceBegin_clicked()
{//开始 掷骰子按钮threadA.diceBegin();ui->btnDiceBegin->setEnabled(false);ui->btnDiceEnd->setEnabled(true);
}void Dialog::on_btnStopThread_clicked()
{//结束线程 按钮threadA.stopThread();//结束线程的run()函数执行threadA.wait();//ui->btnStartThread->setEnabled(true);ui->btnStopThread->setEnabled(false);ui->btnDiceBegin->setEnabled(false);ui->btnDiceEnd->setEnabled(false);
}void Dialog::on_btnStartThread_clicked()
{//启动线程 按钮threadA.start();ui->btnStartThread->setEnabled(false);ui->btnStopThread->setEnabled(true);ui->btnDiceBegin->setEnabled(true);ui->btnDiceEnd->setEnabled(false);
}

详细可见附带源码文件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/19717.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何看待低级爬虫与高级爬虫?

爬虫之所以分为高级和低级&#xff0c;主要是基于其功能、复杂性和灵活性的差异。根据我总结大概有下面几点原因&#xff1a; 功能和复杂性&#xff1a;高级爬虫通常提供更多功能和扩展性&#xff0c;包括处理复杂页面结构、模拟用户操作、解析和清洗数据等。它们解决了开发者…

ctfshow----php特性(89-104)

目录 web89 preg_match函数 、数组 web90 intval()函数、强比较 web91 正则修饰符 web92 intval()函数、弱比较 web93 八进制、小数点 web94 strpos() 函数、小数点 web95 小数点 web96 highlight_file() 下的目录路径 web97 数组 web98 三目运算符 web9…

金融翻译难吗,如何做好金融翻译?

我们知道&#xff0c;金融翻译涉及企业经济这块的&#xff0c;是影响各公司发展很重要的一方面&#xff0c;翻译做得好&#xff0c;可以促进公司内外的交流&#xff0c;及时掌握各种信息&#xff0c;做好应对。那么&#xff0c;金融翻译难吗&#xff0c;如何做好金融翻译&#…

MySQL 极速安装使用与卸载

目录 mysql-5.6.51 极速安装使用与卸载 sqlyog工具 mysql简化 mysql-8.1.0下载配置 再完善 mysql-5.6.51 极速安装使用与卸载 mysql-8.1.0下载安装在后 mysql中国官网 MySQLhttps://www.mysql.com/cn/ 点击MySQL社区服务器 点击历史档案 下载完 解压 用管理员运行cmd&a…

腾讯云从业者认证考试考点——云网络产品

文章目录 腾讯云网络产品功能网络产品概述负载均衡&#xff08;Cloud Load Balancer&#xff09;私有网络&#xff08;Virtual Private Cloud&#xff0c;VPC&#xff09;专线接入弹性网卡&#xff08;多网卡热插拔服务&#xff09;NAT网关&#xff08;NAT Gateway&#xff09;…

「AIGC」智能美学,AI绘画 API 激发无限创意

引言 随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;AI绘画 API 正在以惊人的速度改变艺术创作的面貌。它不仅为艺术家和创作者提供了全新的创作工具&#xff0c;还激发了无限的创意和想象力。在这个智能美学的时代&#xff0c;让我们一起探索 AI 绘画 AP…

客服如何通过微信接收消息通知-唯一客服文档中心

当我们在自己网站上嵌入对接了客服代码&#xff0c;我们想要通过微信接收访客的消息提醒通知&#xff0c;可以通过扫描客服后台的微信二维码&#xff0c;即时收消息通知提醒。 我们网站地址&#xff1a;gofly.v1kf.com 客服后台 后台主页面板&#xff0c;就展示了一个微信二维码…

基于YOLOv5的S弯识别

基于YOLOv5的S弯识别 目录 基于YOLOv5的S弯识别技术背景算法介绍具体实现1、下载仓库2、配置环境3、数据处理4、转成engine文件5、使用代码实现识别 技术总结 技术背景 S弯识别是一个在自动驾驶和机器人领域中很常见的任务&#xff0c;它需要识别道路上的弯道&#xff0c;特别…

BUU CODE REVIEW 1

BUU CODE REVIEW 1 考点&#xff1a;PHP变量引用 源码直接给了 <?phphighlight_file(__FILE__);class BUU {public $correct "";public $input "";public function __destruct() {try {$this->correct base64_encode(uniqid());if($this->c…

Linux权限小结

Linux权限小结 权限的基本介绍 ls -l中显示的内容如下&#xff1a;drwxr-xr-x. 2 yinjun yinjun 87 8月 2 16:24 test&#xff0c;如下图所示 前十位介绍 其中&#xff0c;0-9位为drwxr-xr-x&#xff0c;其说明情况如下 第0位确定文件类型&#xff0c;包括d&#xff0c;-&a…

Linux —— 进程控制

目录 一&#xff0c;进程创建 写时拷贝 二&#xff0c;进程终止 三&#xff0c;进程等待 获取子进程status 一&#xff0c;进程创建 命令行启动命令&#xff08;程序、指令等&#xff09;&#xff1b;通过程序自身fork创建&#xff1b; #include<unistd.h> //子进程…

Spring Boot参数校验实现自定义响应类优雅处理

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; Spring Boot参数校验实现自定义响应类优雅处理 ⏱️ 创作时间&#xf…

AnimateDiff论文解读-基于Stable Diffusion文生图模型生成动画

文章目录 1. 摘要2. 引言3. 算法3.1 Preliminaries3.2. Personalized Animation3.3 Motion Modeling Module 4. 实验5.限制6. 结论 论文&#xff1a; 《AnimateDiff: Animate Your Personalized Text-to-Image Diffusion Models without Specific Tuning》 github: https://g…

数据分析 VS 数据可视化:决战时刻

数据分析和数据可视化是数据科学领域中两个重要的组成部分&#xff0c;很多人不明白两者之间的关系&#xff0c;会误认为是一个东西&#xff0c;其实不然。本文就带大家简单了解一下它们的区别与联系吧&#xff01; 数据分析是指通过收集、处理和解释数据来获取有关特定问题或…

无涯教程-Lua - 嵌套if语句函数

在Lua编程中&#xff0c;您可以在另一个if or else if语句中使用一个if or else if语句。 nested if statements - 语法 嵌套if 语句的语法如下- if( boolean_expression 1) then--[ Executes when the boolean expression 1 is true --]if(boolean_expression 2)then--[ Ex…

ConCurrentHashMap常见面试题

1. JDK1.7和JDK1.8中ConCurrentHashMap的实现有什么不同&#xff1f; JDK1.7中的实现可以认为是大数组套小数组&#xff0c;大数组是Segment数组&#xff0c;小数组是HashEntry数组&#xff0c;锁是锁在大数组的元素上&#xff08;Segment&#xff09;&#xff0c;力度比较大&…

【BASH】回顾与知识点梳理(一)

【BASH】回顾与知识点梳理 一 前言一. 认识与学习 BASH1.1 硬件、核心与 Shell1.2 为何要学文字接口的 shell&#xff1f;1.3 系统的合法 shell 与 /etc/shells 功能1.4 Bash shell 的功能1.5 查询指令是否为 Bash shell 的内建命令&#xff1a; type1.6 指令的下达与快速编辑按…

VS+QT+VTK treeView树型结构模型加载隐藏实例

程序示例精选 VSQTVTK treeView树型结构模型加载隐藏实例 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<VSQTVTK treeView树型结构模型加载隐藏实例>>编写代码&#xff0c;代码…

Chrome 75不支持保存成mhtml的解决方法

在Chrome 75之前&#xff0c;可以设置chrome://flags -> save as mhtml来保存网页为mhtml。 升级新版&#xff0c;发现无法另存为/保存网页为MHTML了。 在网上搜索无果后&#xff0c;只得从chromium项目的commits中查找&#xff0c;原来chrome搞了个"Chrome Flag Owner…

Android 创建 Gradle Task 自动打包并上传至蒲公英

前言 Android 项目日常开发过程中&#xff0c;经常需要打包给到非开发人员验收或调试&#xff0c;例如测试阶段&#xff0c;就要经常基于测试服务器地址&#xff0c;打包安装包&#xff0c;给到组内测试人员进行测试&#xff0c;并且 BUG 修复完成之后也需要再次打包给到测试人…