1. 项目概述

最近研究了一下Qt/C++框架下,windows版本的多进程编写方法,实现了一个小demo。下面详细介绍一下。
MultiProcessDemo是一个基于Qt框架实现的多进程应用程序示例,展示了如何在Windows平台上通过共享内存和事件机制实现进程间通信。该项目主要由一个带GUI的主进程和一个后台存储子进程组成,实现了进程间数据传输、子进程监控与自动重启、优雅退出等功能。

2. 项目架构设计

2.1 整体架构图

┌───────────────────────────┐      ┌─────────────────────────┐
│        主进程 (GUI)        │      │       存储子进程        │
│  ┌─────────────────────┐  │      │  ┌───────────────────┐  │
│  │                     │  │      │  │                   │  │
│  │    MainWindow       │  │      │  │     Storage       │  │
│  │                     │  │      │  │                   │  │
│  └───────────┬─────────┘  │      │  └───────────┬───────┘  │
│              │            │      │              │         │
│              │ 写入数据   │      │              │ 读取数据 │
│              ▼            │      │              ▼         │
│  ┌─────────────────────┐  │      │  ┌───────────────────┐  │
│  │                     │  │      │  │                   │  │
│  │ SharedMemoryManager │<─┼──────┼─>│ SharedMemoryManager │  │
│  │  (创建共享内存)      │  │      │  │  (打开共享内存)      │  │
│  └─────────────────────┘  │      │  └───────────────────┘  │
│              │            │      │              │         │
│              │ 触发事件   │      │              │ 等待事件 │
│              ▼            │      │              ▼         │
│  ┌─────────────────────┐  │      │  ┌───────────────────┐  │
│  │                     │  │      │  │                   │  │
│  │  数据事件 (DataEvent)│<─┼──────┼─>│  数据事件 (DataEvent)│  │
│  │  退出事件 (ExitEvent)│<─┼──────┼─>│  退出事件 (ExitEvent)│  │
│  └─────────────────────┘  │      │  └───────────────────┘  │
└───────────────────────────┘      └─────────────────────────┘▲                                  ▲│                                  │└──────────────────────────────────┘进程间通信

2.2 架构分层

  1. 应用层

    • 主进程:提供用户界面,管理子进程生命周期
    • 存储子进程:后台处理数据,响应主进程指令
  2. 通信层

    • 共享内存:使用Windows API实现高效的进程间数据共享
    • 事件机制:用于进程间同步和通知
  3. 公共组件层

    • 共享定义和工具类:为多进程提供统一的接口和数据结构

3. 核心组件介绍

3.1 SharedMemoryManager

SharedMemoryManager是项目中实现共享内存通信的核心类,封装了Windows API中与共享内存相关的操作。

主要功能

  • 创建/打开共享内存区域
  • 写入数据到共享内存
  • 从共享内存读取数据

关键实现

class SharedMemoryManager {
public:SharedMemoryManager(bool create);  // create=true表示创建共享内存,false表示打开已有共享内存~SharedMemoryManager();bool write(const std::string &data);  // 写入数据std::string read();  // 读取数据private:HANDLE m_hMapFile = nullptr;  // 共享内存句柄bool m_isOwner = false;  // 是否是共享内存的创建者
};

3.2 MainWindow

MainWindow是主进程的GUI界面,负责与用户交互并向子进程发送数据。

主要功能

  • 提供用户交互界面
  • 写入数据到共享内存
  • 触发数据事件通知子进程

关键实现

class MainWindow : public QMainWindow {Q_OBJECT
public:explicit MainWindow(HANDLE dataEvent, QWidget* parent = nullptr);
protected slots:void onButtonClicked();  // 处理按钮点击事件private:HANDLE m_dataEvent;  // 数据事件句柄SharedMemoryManager* m_sharedMemory;  // 共享内存管理器
};

3.3 Storage

Storage是存储子进程的核心类,负责监听事件和处理数据。

主要功能

  • 监听退出事件和数据事件
  • 从共享内存读取数据
  • 处理数据并执行相应操作

关键实现

class Storage {
public:explicit Storage(HANDLE exitEvent, HANDLE dataEvent);int run();  // 运行子进程的主循环private:HANDLE m_exitEvent;  // 退出事件句柄HANDLE m_dataEvent;  // 数据事件句柄SharedMemoryManager* m_sharedMemory;  // 共享内存管理器
};

4. 技术实现细节

4.1 进程创建与管理

项目使用Qt的QProcess类创建和管理子进程,实现了子进程的自动重启和监控机制。

主要流程

  1. 主进程启动时创建全局事件对象
  2. 解析命令行参数,决定是以主进程还是子进程模式运行
  3. 如果是主进程,则创建GUI并启动存储子进程
  4. 设置子进程信号连接,监控其状态
  5. 实现定时器定期检查子进程状态,异常退出时自动重启

关键代码

// 启动 storage 子进程
void startStorage(QProcess *proc, QApplication *app, bool isShuttingDown, std::function<void()> retryFunc) {if (isShuttingDown || proc->state() == QProcess::Running) {return;}QStringList args{"--storage"};proc->start(app->applicationFilePath(), args);if (!proc->waitForStarted(3000)) {QTimer::singleShot(2000, retryFunc);  // 启动失败时重试}
}// 监控 storage 子进程
void setupMonitor(QProcess *proc, QTimer *timer, std::function<void()> restartFunc, bool &isShuttingDown) {QObject::connect(timer, &QTimer::timeout, [=, &isShuttingDown]() {if (isShuttingDown) return;if (proc->state() != QProcess::Running) {restartFunc();  // 检测到子进程未运行,尝试重启}});timer->start(5000);  // 每5秒检查一次
}

4.2 进程间通信机制

项目使用Windows的共享内存和事件机制实现进程间通信,这种方式具有高效、低延迟的特点。

共享内存实现

  • 主进程创建共享内存区域
  • 子进程打开已创建的共享内存区域
  • 通过内存映射文件实现数据共享

事件机制实现

  • 使用全局命名事件实现进程间同步和通知
  • 定义两种事件:数据事件(通知子进程有新数据)和退出事件(通知子进程退出)

关键代码

// 创建全局退出事件
HANDLE createExitEvent() {return CreateEventW(nullptr, TRUE, FALSE, STORAGE_EXIT_EVENT_NAME);
}// 写入数据并通知子进程
void MainWindow::onButtonClicked() {if (m_sharedMemory->write("abc")) {// 通知 storage 有新数据SetEvent(m_dataEvent);}
}// 子进程等待事件并处理
int Storage::run() {HANDLE handles[2] = {m_exitEvent, m_dataEvent};while (true) {DWORD ret = WaitForMultipleObjects(2, handles, FALSE, INFINITE);if (ret == WAIT_OBJECT_0)  // 退出事件{break;}else if (ret == WAIT_OBJECT_0 + 1)  // 数据事件{std::string data = m_sharedMemory->read();// 处理数据...ResetEvent(m_dataEvent);  // 重置事件}}return 0;
}

4.3 优雅退出机制

项目实现了优雅退出机制,确保主进程退出时能够正确通知子进程并等待其退出。

主要流程

  1. 主进程接收到退出信号时,设置关闭标志并停止监控定时器
  2. 触发退出事件通知子进程
  3. 等待子进程在指定时间内正常退出
  4. 如果子进程无响应,则强制终止

关键代码

// 优雅退出 storage
void shutdownStorage(QProcess *proc, HANDLE exitEvent, QTimer *timer, bool &isShuttingDown) {isShuttingDown = true;timer->stop();if (proc->state() == QProcess::Running) {SetEvent(exitEvent);  // 通知子进程退出if (!proc->waitForFinished(5000)) {proc->kill();  // 强制终止proc->waitForFinished(1000);}}if (exitEvent) {CloseHandle(exitEvent);  // 关闭事件句柄}
}

5. 项目结构说明

项目采用清晰的目录结构,将不同功能模块分离,便于维护和扩展。

├── CMakeLists.txt       # 项目构建配置
├── main.cpp             # 程序入口点
├── common/              # 公共组件
│   ├── common.h         # 共享定义和常量
│   ├── sharedmemory_manager.cpp  # 共享内存管理器实现
│   └── sharedmemory_manager.h    # 共享内存管理器定义
├── dataview/            # 主进程界面相关
│   ├── mainwindow.cpp   # 主窗口实现
│   └── mainwindow.h     # 主窗口定义
└── storage/             # 存储子进程相关├── storage.cpp      # 存储子进程实现└── storage.h        # 存储子进程定义

6. 关键技术点分析

6.1 共享内存的安全性考虑

共享内存在提供高效通信的同时,也带来了一些安全性问题,本项目主要考虑了以下几点:

  • 使用互斥事件确保数据读写的同步
  • 限制共享内存大小,防止内存滥用
  • 进程异常退出时的资源清理

6.2 子进程监控与自动恢复

项目实现了完善的子进程监控机制,确保系统的稳定性和可靠性:

  • 定时检查子进程状态
  • 捕获子进程异常退出信号
  • 实现自动重启逻辑,保证服务可用性

6.3 Windows API与Qt框架的融合

项目成功融合了Windows API和Qt框架的优势:

  • 使用Qt框架快速构建GUI和管理应用程序生命周期
  • 利用Windows原生API实现高效的进程间通信
  • 通过信号槽机制简化事件处理和组件间通信

6.4 为何不使用QSharedMemory

项目选择自行实现基于Windows API的SharedMemoryManager而非使用Qt提供的QSharedMemory类,主要是通过直接调用底层 API(如 CreateFileMappingW),可以针对 Windows 系统特性进行优化,减少 Qt 封装带来的开销,同时获得更灵活的共享内存控制和更高效的进程间同步能力。

7. 多进程架构的优势

本项目采用多进程架构而非传统的多线程架构,主要基于以下技术优势:

  1. 更高的稳定性和容错性:一个进程崩溃不会影响其他进程的运行,提高了整个应用的稳定性
  2. 更好的资源隔离:进程间内存空间完全隔离,避免了共享内存访问冲突和资源竞争问题
  3. 充分利用多核性能:多进程可以更有效地利用多核CPU资源,实现真正的并行计算

虽然多进程架构在进程间通信上会有一定开销,但对于需要高稳定性、强隔离性的应用场景,这些优势远大于其带来的额外成本。

8. 程序输出展示

在这里插入图片描述

9. 总结与展望

MultiProcessDemo项目成功实现了基于Qt的多进程应用架构,展示了如何在Windows平台上实现高效的进程间通信。该项目的设计理念和实现方法可以应用于需要将UI和后台处理分离的应用场景,有助于提高应用程序的稳定性和响应性能。

未来改进方向

  1. 共享内存队列:目前共享内存通信使用的是简单的字符串传递,考虑引入队列机制,支持批量数据传输。
  2. 增加其他子进程:本demo只是实现了基本的功能,后续可根据需求新增其他子进程。。

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

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

相关文章

Android SystemServer 系列专题【篇五:UserController用户状态控制】

本篇接着SystemServer的启动流程&#xff0c;围绕SystemServer最后阶段关于主用户的启动和解锁的流程&#xff0c;作为切入点&#xff0c;来看看SystemServer是如何讲用户状态同步到所有的系统级服务中。ssm.onStartUserssm.onUnlockingUserssm.onUnlockedUser本篇先介绍UserCo…

推荐使用 pnpm 而不是 npm

npm 的局限性 磁盘空间浪费在 npm 早期版本中&#xff0c;每个项目的node_modules目录都会完整复制所有依赖包&#xff0c;即使多个项目依赖同一个包的相同版本&#xff0c;也会重复存储。这导致磁盘空间被大量占用&#xff0c;随着项目数量的增加&#xff0c;存储成本显著上升…

Transformer实战(18)——微调Transformer语言模型进行回归分析

Transformer实战&#xff08;18&#xff09;——微调Transformer语言模型进行回归分析0. 前言1. 回归模型2. 数据处理3. 模型构建与训练4. 模型推理小结系列链接0. 前言 在自然语言处理领域中&#xff0c;预训练 Transformer 模型不仅能胜任离散类别预测&#xff0c;也可用于连…

【Linux】【实战向】Linux 进程替换避坑指南:从理解 bash 阻塞等待,到亲手实现能执行 ls/cd 的 Shell

前言&#xff1a;欢迎各位光临本博客&#xff0c;这里小编带你直接手撕&#xff0c;文章并不复杂&#xff0c;愿诸君耐其心性&#xff0c;忘却杂尘&#xff0c;道有所长&#xff01;&#xff01;&#xff01;&#xff01; IF’Maxue&#xff1a;个人主页&#x1f525; 个人专栏…

linux常用命令 (3)——系统包管理

博客主页&#xff1a;christine-rr-CSDN博客 ​​​​​ ​​ hi&#xff0c;大家好&#xff0c;我是christine-rr ! 今天来分享一下linux常用命令——系统包管理 目录linux常用命令---系统包管理&#xff08;一&#xff09;Debian 系发行版&#xff08;Ubuntu、Debian、Linux …

YOLOv8 mac-intel芯片 部署指南

&#x1f680; 在 Jupyter Notebook 和 PyCharm 中使用 Conda 虚拟环境&#xff08;YOLOv8 部署指南&#xff0c;Python 3.9&#xff09; YOLOv8 是 Ultralytics 开源的最新目标检测模型&#xff0c;轻量高效&#xff0c;支持分类、检测、分割等多种任务。 在 Mac&#xff08;…

【高等数学】第十一章 曲线积分与曲面积分——第六节 高斯公式 通量与散度

上一节&#xff1a;【高等数学】第十一章 曲线积分与曲面积分——第五节 对坐标的曲面积分 总目录&#xff1a;【高等数学】 目录 文章目录1. 高斯公式2. 沿任意闭曲面的曲面积分为零的条件3. 通量与散度1. 高斯公式 设空间区域ΩΩΩ是由分片光滑的闭曲面ΣΣΣ所围成&#x…

IDEA试用过期,无法登录,重置方法

IDEA过期&#xff0c;重置方法: IntelliJ IDEA 2024.2.0.2 (亲测有效) 最新Idea重置办法!&#xff1a; 方法一&#xff1a; 1、删除C:\Users\{用户名}\AppData\Local\JetBrains\IntelliJIdea2024.2 下所有文件(注意&#xff1a;是子目录全部删除) 2、删除C:\Users\{用户名}\App…

创建用户自定义桥接网络并连接容器

1.创建用户自定义的 alpine-net 网络[roothost1 ~]# docker network create --driver bridge alpine-net 9f6d634e6bd7327163a9d83023e435da6d61bc6cf04c9d96001d1b64eefe4a712.列出 Docker 主机上的网络[roothost1 ~]# docker network ls NETWORK ID NAME DRIVER …

Vue3 + Vite + Element Plus web转为 Electron 应用,解决无法登录、隐藏自定义导航栏

如何在vue3 Vite Element Plus搭好的架构下转为 electron应用呢&#xff1f; https://www.electronjs.org/zh/docs/latest/官方文档 https://www.electronjs.org/zh/docs/latest/ 第一步&#xff1a;安装 electron相关依赖 npm install electron electron-builder concurr…

qt QAreaLegendMarker详解

1. 概述QAreaLegendMarker 是 Qt Charts 模块中的一部分&#xff0c;用于在图例&#xff08;Legend&#xff09;中表示 QAreaSeries 的标记。它负责显示区域图的图例项&#xff0c;通常包含区域颜色样例和对应的描述文字。图例标记和对应的区域图关联&#xff0c;显示区域的名称…

linux 函数 kstrtoul

kstrtoul 函数概述 kstrtoul 是 Linux 内核中的一个函数&#xff0c;用于将字符串转换为无符号长整型&#xff08;unsigned long&#xff09;。该函数定义在 <linux/kernel.h> 头文件中&#xff0c;常用于内核模块中解析用户空间传递的字符串参数。 函数原型 int kstrtou…

LLM(三)

一、人类反馈的强化学习&#xff08;RLHF&#xff09;微调的目标是通过指令&#xff0c;包括路径方法&#xff0c;进一步训练你的模型&#xff0c;使他们更好地理解人类的提示&#xff0c;并生成更像人类的回应。RLHF&#xff1a;使用人类反馈微调型语言模型&#xff0c;使用强…

DPO vs PPO,偏好优化的两条技术路径

1. 背景在大模型对齐&#xff08;alignment&#xff09;里&#xff0c;常见的两类方法是&#xff1a;PPO&#xff1a;强化学习经典算法&#xff0c;OpenAI 在 RLHF 里用它来“用奖励模型更新策略”。DPO&#xff1a;2023 年提出的新方法&#xff08;参考论文《Direct Preferenc…

BLE6.0信道探测,如何重构物联网设备的距离感知逻辑?

在物联网&#xff08;IoT&#xff09;无线通信技术快速渗透的当下&#xff0c;实现人与物、物与物之间对物理距离的感知响应能力已成为提升设备智能高度与人们交互体验的关键所在。当智能冰箱感知用户靠近而主动亮屏显示内部果蔬时、当门禁系统感知到授权人士靠近而主动开门时、…

【计算机 UTF-8 转换为本地编码的含义】

UTF-8 转换为本地编码的含义 详细解释一下"UTF-8转换为本地编码"的含义以及为什么在处理中文时这很重要。 基本概念 UTF-8 编码 国际标准&#xff1a;UTF-8 是一种能够表示世界上几乎所有字符的 Unicode 编码方式跨平台兼容&#xff1a;无论在哪里&#xff0c;UTF-8 …

4.6 变体

1.变体简介 2.为什么需要变体 3.变体是如何产生的 4.变体带来的麻烦 5.multi_compile和shader_feature1.变体简介 比如我们开了一家餐厅, 你有一本万能的菜单(Shader源代码), 上面包含了所有可能的菜式; 但是顾客每次来点餐时, 不可能将整本菜单都做一遍, 他们会根据今天有没有…

猿辅导Android开发面试题及参考答案(下)

为什么开发中要使用线程池,而不是直接创建线程(如控制线程数量、复用线程、降低开销)? 开发中优先使用线程池而非直接创建线程,核心原因是线程池能优化线程管理、降低资源消耗、提高系统稳定性,而直接创建线程存在难以解决的缺陷,具体如下: 控制线程数量,避免资源耗尽…

【网络通信】IP 地址深度解析:从技术原理到企业级应用​

IP 地址深度解析&#xff1a;从技术原理到企业级应用​ 文章目录IP 地址深度解析&#xff1a;从技术原理到企业级应用​前言一、基础认知&#xff1a;IP 地址的技术定位与核心特性​1.1 定义与网络层角色1.2 核心属性与表示法深化二、地址分类&#xff1a;从类别划分到无类别路…

grafana实践

一、如何找到grafana的插件目录 whereis grafana grafana: /etc/grafana /usr/share/grafana插件安装目录、默认安装目录&#xff1a; 把vertamedia-clickhouse-datasource-3.4.4.zip解压到下面目录&#xff0c;然后重启就可以了 /var/lib/grafana/plugins# 6. 设置权限 sudo …