当前位置: 首页 > news >正文

Linux探秘坊-------13.进程间通信

1.进程间通信⽬的

在这里插入图片描述

2.管道

在这里插入图片描述
在这里插入图片描述

2.1 匿名管道

-----通常用来实现 父子通信

创建子进程时,需要把父进程的进程内容全部拷贝一份,但文件管理是不需要拷贝的 但是我们把父进程的文件描述符表给拷贝下来了,文件描述符表里是一堆指针他们仍然指向父进程打开的那些文件

在这里插入图片描述

  • 这也是为什么之前运行子进程会在同一个屏幕上打印内容,因为父子进程用的是同一个显示器文件自然在同一个屏幕上打印咯,
  • 和c++中遇到的 浅拷贝 十分相似

在这里插入图片描述

在这里插入图片描述

2.2 原理

在这里插入图片描述

2.2 管道样例

#include <iostream>
#include <unistd.h>using namespace std;
int main()
{int fd[2]={0};//这里使用fd模拟文件描述符表,忽略了0,1,2即标准输入stdin,标准输出stdout,标准错误stderrint n=pipe(fd);//pipe函数需要头文件unistd.hif(n<0)//运行失败会是n<0{cout<<"error"<<endl;return 1;}cout<<"fd[0]:"<<fd[0]<<endl;cout<<"fd[1]:"<<fd[1]<<endl;return 0;
}

在这里插入图片描述

最终结果是:

在这里插入图片描述

  • 因为0,1,2即标准输入,标准输出,标准错误一直在被打开,所以只能分配3,4

完整父子进程管道代码:

#include <iostream>      // 标准输入输出(cout, endl)
#include <unistd.h>      // 提供 pipe(), fork(), close(), read(), write(), sleep() 等系统调用
#include <cstdio>        // 提供 printf() 等 C 标准 I/O 函数
#include <cstring>       // 提供字符串处理函数(如 memset)
#include <sys/types.h>   // 提供 pid_t 等数据类型定义
#include <sys/wait.h>    // 提供 waitpid() 函数using namespace std;// 子进程向管道写入数据的函数
void childwrite(int wfd) {char c = 0;          // 写入的字符(这里固定为 0)int cnt = 0;         // 计数器,记录写入次数while (true) {write(wfd, &c, 1); // 向管道写入 1 字节(实际写入的是 '\0')printf("child: %d\n", cnt++); // 打印写入次数}
}// 父进程从管道读取数据的函数
void fatherread(int rfd) {char buffer[1024];    // 读取缓冲区while (true) {sleep(100);       // 父进程休眠 100 秒(实际会被 read() 打断)buffer[0] = 0;    // 清空缓冲区(可选)// 从管道读取数据(最多读 sizeof(buffer)-1 字节,预留 1 字节给 '\0')ssize_t n = read(rfd, buffer, sizeof(buffer)-1);if (n > 0) {      // 读取成功buffer[n] = 0; // 手动添加字符串结束符 '\0'std::cout << "child say: " << buffer << std::endl; // 打印读取的内容} else if (n == 0) { // 管道写端关闭(子进程退出)std::cout << "n : " << n << std::endl;std::cout << "child 退出,我也退出";break;} else {          // 读取错误break;}break;            // 测试时提前退出循环(实际应去掉)}
}int main() {// 1. 创建管道int fd[2] = {0};      // fd[0]:读端,fd[1]:写端int n = pipe(fd);     // 调用 pipe() 创建匿名管道if (n < 0) {          // 创建失败cout << "error" << endl;return 1;}cout << "fd[0]:" << fd[0] << endl; // 打印读端 fdcout << "fd[1]:" << fd[1] << endl; // 打印写端 fd// 2. 创建子进程pid_t pid = fork();   // 调用 fork() 创建子进程if (pid == 0) {       // 子进程逻辑close(fd[0]);     // 关闭读端(子进程只写)childwrite(fd[1]); // 调用子进程写入函数close(fd[1]);     // 关闭写端(实际不会执行到这里)exit(0);          // 子进程退出}sleep(5);             // 父进程休眠 5 秒(等待子进程写入数据)close(fd[1]);         // 关闭写端(父进程只读)fatherread(fd[0]);    // 调用父进程读取函数close(fd[0]);         // 关闭读端// 等待子进程退出int status = 0;int ret = waitpid(pid, &status, 0); // 阻塞等待子进程结束if (ret > 0) {        // 子进程已退出// 打印子进程退出状态(高 8 位是退出码,低 7 位是终止信号)printf("exit code: %d, exit signal: %d\n", (status>>8)&0xFF, status&0x7F);sleep(5);         // 父进程再休眠 5 秒(观察用)}return 0;
}

2.3 五种特性

在这里插入图片描述

2.4 四种通信情况

在这里插入图片描述

blog.csdnimg.cn/direct/eeef895593df4fd08b31442035e93198.png)

3.进程池的模拟

在这里插入图片描述

3.1 hpp文件的使用

#ifndef __PROCESS_POOL_HPP__  // 头文件保护宏(双下划线风格)
#define __PROCESS_POOL_HPP__#include <iostream>  // 系统头文件用尖括号<>// 函数声明/定义
void test() {std::cout << "test" << std::endl;  // 直接使用std::前缀
}#endif  // __PROCESS_POOL_HPP__

-函数的声明和定义可以放在一块写,注意头两行和末尾一行 是格式

3.2 进程池代码实现

ProcessPool.hpp:

#ifndef __PROCESS_POOL_HPP__  // 头文件保护宏,防止重复包含
#define __PROCESS_POOL_HPP__#include <iostream>      // 标准输入输出
#include <cstdlib>       // C标准库(替代stdlib.h的C++版本)
#include <vector>        // 动态数组容器
#include <unistd.h>      // POSIX API(pipe/fork/close等)
#include <sys/wait.h>    // 进程等待相关函数
#include "Task.hpp"      // 自定义任务管理头文件// Channel类:管理单个子进程的通信通道
class Channel
{
public:// 构造函数:初始化写端fd和子进程IDChannel(int fd, pid_t id) : _wfd(fd), _subid(id){// 生成通道名称(格式:channel-[fd]-[pid])_name = "channel-" + std::to_string(_wfd) + "-" + std::to_string(_subid);}// 析构函数(空实现,资源通过Close()显式释放)~Channel() {}// 向子进程发送任务码void Send(int code){int n = write(_wfd, &code, sizeof(code));(void)n; // 显式忽略返回值(避免编译器警告)}// 关闭写端文件描述符void Close(){close(_wfd); // 关闭管道写端}// 等待子进程退出,回收子进程,避免僵尸进程出现void Wait(){pid_t rid = waitpid(_subid, nullptr, 0); // 阻塞等待(void)rid; // 显式忽略返回值}// Getter方法int Fd() { return _wfd; }             // 获取写端fdpid_t SubId() { return _subid; }      // 获取子进程PIDstd::string Name() { return _name; }  // 获取通道名称private:int _wfd;            // 管道写端文件描述符pid_t _subid;        // 子进程PIDstd::string _name;   // 通道标识名称
};// ChannelManager类:管理所有子进程通道
class ChannelManager
{
public:ChannelManager() : _next(0) {}  // 初始化轮询索引// 添加新通道void Insert(int wfd, pid_t subid){_channels.emplace_back(wfd, subid); // 原地构造Channel对象,加入channel数组}// 轮询选择下一个通道(简单负载均衡)Channel &Select(){auto &c = _channels[_next];_next = (_next + 1) % _channels.size(); // 环形选择return c;}// 打印所有通道信息void PrintChannel(){for (auto &channel : _channels){std::cout << channel.Name() << std::endl;}}// 关闭所有子进程管道void StopSubProcess(){for (auto &channel : _channels){channel.Close();//关掉读std::cout << "关闭: " << channel.Name() << std::endl;}}// 回收所有子进程void WaitSubProcess(){for (auto &channel : _channels){channel.Wait();std::cout << "回收: " << channel.Name() << std::endl;}}~ChannelManager() {}  // 析构函数(vector自动释放)private:std::vector<Channel> _channels;  // 存储所有Channel对象int _next;                       // 轮询索引
};const int gdefaultnum = 5;  // 默认子进程数量// ProcessPool类:主进程池实现
class ProcessPool
{
public:// 构造函数:初始化进程数并注册任务ProcessPool(int num) : _process_num(num){_tm.Register(PrintLog);    // 注册日志任务_tm.Register(Download);    // 注册下载任务_tm.Register(Upload);      // 注册上传任务}//把这三个函数指针全部加入函数指针数组中// 子进程工作循环void Work(int rfd){while (true){int code = 0;ssize_t n = read(rfd, &code, sizeof(code));//从rfd中读任务吗,和channel的send函数相对应,正常一次读4字节if (n > 0)  // 成功读取{if (n != sizeof(code)) continue;  // 数据不完整则继续读取std::cout << "子进程[" << getpid() << "]收到任务码: " << code << std::endl;_tm.Execute(code);  // 执行对应任务,就是三个函数之一,上传,下载。。。。}else if (n == 0)  // 管道关闭(父进程终止){std::cout << "子进程退出" << std::endl;break;}else  // 读取错误{std::cerr << "读取错误" << std::endl;break;}}}// 启动进程池bool Start(){for (int i = 0; i < _process_num; i++){// 1. 创建管道int pipefd[2] = {0};if (pipe(pipefd) < 0) return false;  // 创建失败// 2. 创建子进程pid_t subid = fork();if (subid < 0) return false;  // fork失败if (subid == 0)  // 子进程分支{close(pipefd[1]);  // 关闭写端Work(pipefd[0]);    // 进入工作循环close(pipefd[0]);exit(0);            // 正常退出}else  // 父进程分支{close(pipefd[0]);            // 关闭读端_cm.Insert(pipefd[1], subid); // 记录通道信息}}return true;}// 调试用:打印所有通道void Debug() { _cm.PrintChannel(); }// 运行任务(主进程调用)void Run(){int taskcode = _tm.Code();          // 1. 获取任务码auto &c = _cm.Select();             // 2. 选择子进程std::cout << "选择子进程: " << c.Name() << std::endl;c.Send(taskcode);                   // 3. 发送任务std::cout << "发送任务码: " << taskcode << std::endl;}// 停止进程池void Stop(){_cm.StopSubProcess();  // 关闭所有管道_cm.WaitSubProcess();  // 回收所有子进程}~ProcessPool() {}  // 析构函数private:ChannelManager _cm;      // 通道管理器int _process_num;        // 子进程数量TaskManager _tm;         // 任务管理器
};

Task.hpp:

// 防止头文件被重复包含的编译器指令(现代C++替代#ifndef的方式)
#pragma once// 标准输入输出库(用于cout等)
#include <iostream>
// 动态数组容器(用于存储任务函数指针)
#include <vector>
// 时间相关函数(用于随机数种子初始化)
#include <ctime>// 定义函数指针类型:无参数、无返回值的函数,名字是task_t!!!!!
typedef void (*task_t)(); 调试用任务函数 
// 打印日志任务函数
void PrintLog()
{std::cout << "我是一个打印日志的任务" << std::endl;
}// 下载任务函数 
void Download()
{std::cout << "我是一个下载的任务" << std::endl;
}// 上传任务函数
void Upload()
{std::cout << "我是一个上传的任务" << std::endl;
}
//// 任务管理类
class TaskManager
{
public:// 构造函数:初始化随机数种子TaskManager(){srand(time(nullptr)); // 用当前时间初始化随机数生成器}// 注册任务函数:将函数指针存入vectorvoid Register(task_t t){_tasks.push_back(t); // 添加到任务列表末尾}// 生成随机任务码:返回[0, 任务数量-1]的随机数int Code(){return rand() % _tasks.size(); // 取模保证不越界}// 执行任务:根据code调用对应的函数void Execute(int code){// 检查code是否合法(防御性编程)if(code >= 0 && code < _tasks.size()){_tasks[code](); // 通过函数指针调用任务,就是上面三个打印,上传,下载函数}// 注意:未处理非法code的情况(可添加错误处理)}// 析构函数(当前为空实现)~TaskManager(){}private:std::vector<task_t> _tasks; // 存储所有注册的任务函数指针
};

makefile:

process_pool:Main.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f process_pool

main.cc:

#include "ProcessPool.hpp"int main()
{// 创建进程池对象ProcessPool pp(gdefaultnum);// 启动进程池pp.Start();//刚开始就是建立5个子进程和通道,但通道内没有内容即任务码,所以子进程的work会被卡住。// 自动派发任务int cnt = 10;while(cnt--){pp.Run();//往子进程里去发放任务码,子进程开始work,也就是开始调用manager的Execute函数,就是在三个上传,下载函数中随机选一个来执行sleep(1);}// 回收,结束进程池pp.Stop();// 关闭所有管道-即回收父进程的wfd---使用close函数关掉所有channel中的wfd//回收所有子进程----调用waitpid函数return 0;
}
http://www.lryc.cn/news/579970.html

相关文章:

  • 五、Flutter动画
  • 【AI总结】Git vs GitHub vs GitLab:深度解析三者联系与核心区别
  • 【Git】git命令合集
  • 网安系列【4】之OWASP与OWASP Top 10:Web安全入门指南
  • Rust 闭包
  • 暴雨服务器成功中标华中科技大学集成电路学院服务器采购项目
  • 封装一个png的编码解码操作
  • 数据库位函数:原理、应用与性能优化
  • 企业该怎么做竞争分析?一文了解
  • Linux-进程概念(3)
  • 【WEB】Polar靶场 6-10题 详细笔记
  • 类图+案例+代码详解:软件设计模式----原型模式
  • vue3 el-table 行筛选 设置为单选
  • 电商分拣的“效率密码”:艾立泰轻量化托盘引领自动化流水线革新
  • vue3 获取选中的el-table行数据
  • 【WRFDA第三期】OBSPROC namelist 变量总结
  • Ubuntu 22.04 + MySQL 8 无密码登录问题与 root 密码重置指南
  • OpenCV中DPM(Deformable Part Model)目标检测类cv::dpm::DPMDetector
  • 前端基础知识Webpack系列 - 03(webpack中常见的Loader?解决了什么问题?)
  • STM32CubeMX教程1 实现点灯点灯
  • 量化开发(系列第3篇): C++在高性能量化交易中的核心应用与技术栈深度解析
  • 三态逻辑详解:单片机GPIO、计算机总线系统举例
  • 【python实用小脚本-128】基于 Python 的 Hacker News 爬虫工具:自动化抓取新闻数据
  • RK-Android11-性能优化-限制App内存上限默认512m
  • 基于Hadoop的公共自行车数据分布式存储和计算平台的设计与实现
  • 如何调节笔记本电脑亮度?其实有很多种方式可以调整亮度
  • Mysql+neo4j创建节点和关系
  • [环境安装] 图数据库Neo4j 2025.05 安装(apple M芯片)
  • XILINX Kintex 7系列FPGA的全局时钟缓冲器(BUFG)和区域时钟缓冲器(BUFR/BUFH)的区别
  • EPLAN 电气制图:建立自己的部件库,添加部件-加SQL Server安装教程(三)上