网站推广站点建设与策划,网络加速器海外,简单的个人网站下载,当当网站建设目标1.进程间通信⽬的 2.管道 2.1 匿名管道
-----通常用来实现 父子通信
创建子进程时#xff0c;需要把父进程的进程内容全部拷贝一份#xff0c;但文件管理是不需要拷贝的 但是我们把父进程的文件描述符表给拷贝下来了#xff0c;文件描述符表里是一堆指针#xff0c;他们仍…1.进程间通信⽬的 2.管道 2.1 匿名管道
-----通常用来实现 父子通信
创建子进程时需要把父进程的进程内容全部拷贝一份但文件管理是不需要拷贝的 但是我们把父进程的文件描述符表给拷贝下来了文件描述符表里是一堆指针他们仍然指向父进程打开的那些文件 这也是为什么之前运行子进程会在同一个屏幕上打印内容因为父子进程用的是同一个显示器文件自然在同一个屏幕上打印咯,和c中遇到的 浅拷贝 十分相似 2.2 原理 2.2 管道样例
#include iostream
#include unistd.husing namespace std;
int main()
{int fd[2]{0};//这里使用fd模拟文件描述符表忽略了012即标准输入stdin标准输出stdout标准错误stderrint npipe(fd);//pipe函数需要头文件unistd.hif(n0)//运行失败会是n0{couterrorendl;return 1;}coutfd[0]:fd[0]endl;coutfd[1]:fd[1]endl;return 0;
}最终结果是 因为012即标准输入标准输出标准错误一直在被打开所以只能分配34
完整父子进程管道代码
#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; // 写入的字符这里固定为 0int cnt 0; // 计数器记录写入次数while (true) {write(wfd, c, 1); // 向管道写入 1 字节实际写入的是 \0printf(child: %d\n, cnt); // 打印写入次数}
}// 父进程从管道读取数据的函数
void fatherread(int rfd) {char buffer[1024]; // 读取缓冲区while (true) {sleep(100); // 父进程休眠 100 秒实际会被 read() 打断buffer[0] 0; // 清空缓冲区可选// 从管道读取数据最多读 sizeof(buffer)-1 字节预留 1 字节给 \0ssize_t n read(rfd, buffer, sizeof(buffer)-1);if (n 0) { // 读取成功buffer[n] 0; // 手动添加字符串结束符 \0std::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, (status8)0xFF, status0x7F);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 APIpipe/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::vectorChannel _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; // 任务管理器
};#endifTask.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::vectortask_t _tasks; // 存储所有注册的任务函数指针
};makefile
process_pool:Main.ccg -o $ $^ -stdc11
.PHONY:clean
clean:rm -f process_poolmain.cc:
#include ProcessPool.hppint 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;
}小问题 如果我每关一个wfd回收一个子进程会怎样 会在第一个子进程回收时阻塞-------------------------------------- read没有返回0 第一次产生子进程父进程文件描述符表分配34三是读四是写子进程先是拷贝父类的内容所以子进程也是三是读四是写然后关闭不需要的fd父进程关闭3子进程关闭4第二次产生子进程父进程分配文件描述符35因为4已经在上次的过程中被占用三是读五是写同理子进程也是三是读五是写然后再次关闭不需要的fd父进程关闭3子进程关闭5 要注意的是第二次产生的子进程会继承父进程的4即二号子进程的4是指向第一个子进程管道的写端的所以第一次父进程的wfd被关闭后写端并没有完全关闭因为剩余的四个子进程都继承了4号写端计数器还有4read函数就不会返回0自然就没办法结束第一个子进程自然就没办法使用wait函数回收导致阻塞 解决办法-----倒着关因为只有最后一个子进程的写端是由父进程一人持有的父进程关了那就是真的关了可以让read直接返回0完成回收----以此类推也可以在子进程创立时遍历channel数组把里面的wfd都关了说白了就是把继承下来的写端全关了这样所有的写端都只由父进程持有
4.命名管道