织梦资源下载站网站模板,七牛镜像存储 wordpress 插件,asp网站建设技术方案,网站横条广告目录
一#xff0c;理解进程之间的通信
1. 进程间通信目的
2. 进程间通信的技术背景
3#xff0c;常见的进程间通信
二#xff0c;管道
1. 尝试建立一个管道
管道的特点#xff1a;
管道提供的访问控制#xff1a;
2. 扩展#xff1a;进程池
阶段一#xff1a…目录
一理解进程之间的通信
1. 进程间通信目的
2. 进程间通信的技术背景
3常见的进程间通信
二管道
1. 尝试建立一个管道
管道的特点
管道提供的访问控制
2. 扩展进程池
阶段一 创建多个子进程
阶段二构建命令方法
ProcessPool.cpp
task.hpp
下一期进程通信基础知识
结语 一理解进程之间的通信
首先系统在设计时秉持这相互独立的原则因此要想实现进程之间的通信是比较困难的。而进程之间的通信本质上是不同的进程能访问同一份数据。 1. 进程间通信目的
数据传输一个进程需要将它的数据发送给另一个进程。 资源共享多个进程之间共享同样的资源。 通知事件一个进程需要向另一个或一组进程发送消息通知它它们发生了某种事件如进程终止时要通知父进程。 进程控制有些进程希望完全控制另一个进程的执行如Debug进程此时控制进程希望能够拦截另一个进程的所有陷入和异常并能够及时知道它的状态改变。实现多进程协同工作比如A进程完成数据收集整合B进程接受A进程结果分析问题。 2. 进程间通信的技术背景
1进程是具有独立性的虚拟地址 页表 保证进程之间的独立性 内核中数据结构 代码以及数据
2通信成本比较高。 3常见的进程间通信
1. Linux系统——管道
2. SystemV——单机通信(多进程)
3. posix——网络通信 二管道 什么是管道 管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道。 而这个 “ 管道 ”虽然是一个文件但它不向硬盘写入管道里的数据是内存级的临时数据。 fd 这个变量就是我们父进程创建的那个pipefile管道文件描述符存储数组在经过pipe创建后将管道的文件描述符填入fd数组中。 1. 尝试建立一个管道
#include iostream
#include unistd.h
#include string
#include assert.h
#include sys/types.h
using namespace std;
int main()
{// 用数组记录读写端int pipefd[2] {0}; // pipefd[0],是读端0就如一张口pipefd[1],是写端1就如一只笔。int ret pipe(pipefd);assert(ret ! -1);pid_t pd fork();// 假设父进程需要写。if (pd 0){ // 子进程创建成功// 子进程则需要的是读close(pipefd[1]);cout chail success endl;char red[1024];// 循环接收并打印数据while (1){read(pipefd[0], red, sizeof (red) -1);cout red ### 我是子进程,pid: getpid() 父进程: getppid() endl;}exit(1);}// 父进程 功能写close(pipefd[0]);string base_str 我是父进程, 正在给你发消息:;char buffer[1024]; // 输出缓冲区int count 0;while (1) // 不断地向子进程发送数据{// 向缓冲区里不断输入变换的消息int len snprintf(buffer, sizeof buffer, %s pid [%d], 消息数量: %d, base_str.c_str(), getpid(), count);// 向管道写入write(pipefd[1], buffer, len);sleep(1); // 一秒秒的刷新} return 0;
} 发现现象
我们是否注意到在父进程写数据时是每隔一秒写一份到管道而我们没有限制子进程打印的间隔但从结果来看向管道内取数据子进程打印过程似乎受到了什么控制。
我们尝试将父进程的写入间隔设置为默认0将子进程的读出时间设置为10秒一次读。结果上我们能看到父进程在写入到一定次数后停止了写入子进程经过10秒后会打印一大堆数据直到打印完为止。从这个现象我们可以发现 管道提供访问控制。管道本质上也是一个文件那么也有其最大容量当即将满时暂停写入等待空间当管道为空时读操作将进行等待等待数据写入。 管道的特点 1. 管道是用来父子关系的通信管道是具有继承性的。
2. 管道可以提供进程间通信提供访问控制。
3. 管道提供面向流试的通信服务字节流——需要协议进行数据区分后面我们再聊。
4. 管道是基于文件的文件的生命周期是随进程的因此管道的生命周期也是随进程的。
5. 管道是单向通信属于半双工通信的一种半双工意思是永远只有一方在写一方在读全双工则可以双方在写双方在读 管道提供的访问控制 2. 扩展进程池
相关知识
1.unin32_t 类型
uint32_t是一个无符号32位整数类型其意义在于表示一个无符号的32位整数范围为0到4294967295。使用uint32_t类型可以确保数据的范围和位数符合要求同时提高代码的可移植性和可读性。这样不管在任何一种型号的机器上sizeof取得的字节数都是4字节。ssize_t类型表示有符号的大小类型通常用于表示某些系统调用的返回值或参数。它的大小通常与机器的字长相同即32位机器上为4字节64位机器上为8字节。ssize_t类型通常用于表示读取或写入的字节数。
2.functiaonal 头文件
头文件 functional 是 C 标准库中的一个头文件它包含了一些模板类和函数对象用于支持函数式编程和泛型编程。这些类和函数对象可以帮助程序员编写更加灵活和抽象的代码提高代码的可读性和可维护性。它还提供了一些算法和函数如 std::function、std::bind、std::mem_fn 等用于处理函数对象和函数指针以及实现函数的组合、绑定和适配等功能。
因此头文件 functional 的意义在于为 C 程序员提供了一些强大的工具帮助他们更好地利用函数式编程和泛型编程的特性提高代码的质量和效率。 首先什么是进程池 通俗的理解为子进程替父进程进行数据处理。
父进程为了在主程序中完成任务需要让子进程去处理数据整理整合数据但为了提高创建父进程的效率在父进程还未向子进程派发任务前提前创建多个子进程然后通过管道进行命令传递。 阶段一 创建多个子进程
通过for循环创建多个子进程并通过管道联系
阶段二构建命令方法
我们将方法单独一个文件向管道中读取命令信息然后根据命令信息调用对应的方法。
ProcessPool.cpp
#include iostream
#include assert.h
#include vector
#include unistd.h
#include Task.hpp //导入的方法文件
#include time.h
#include sys/types.h
#include sys/wait.husing namespace std;#define NAM_OF_PRO 5// 对命令进行处理
int waitcommand(int fd, bool quit)
{uint32_t command 0; // 取个32位的类型用来获取4字节的数据ssize_t byte read(fd, command, sizeof command);// 如果父进程没有发送 if (byte 0) {quit true; // 命令管道开启 //你将他开启了return 0;}// 检测是否是4字节的命令消息1assert(byte sizeof (uint32_t));return command;
}int main(){ // 阶段一 创建多个子进程// 放pid 与 创建时所在的端口——fdvectorpairpid_t, int slots; // slots 位置的意思 // 创建多个进程for (int i 0; i NAM_OF_PRO; i){// 建立管道int pipefil[2] {0};int ret pipe(pipefil);assert(ret ! -1); // 管道检测// 创建子进程pid_t fd fork();if (fd 0) // 子进程{// 关闭写端close(pipefil[1]);while (1){ // 处理子进程任务//未接收数据子进程进入阻塞状态// 阶段二为子进程设置任务bool quit false; // 管道是否被使用int k waitcommand(pipefil[0], quit); // 进入等待命令函数// cout K: k quit quit endl;if (quit)break; // 目的当我们开始关闭管道时read()会读取0字节这时让子进程退出tasklist[k](); // 利用函数容器直接调用方法函数} exit(1);}// 父进程关闭读端close(pipefil[0]);// 同时记录子进程pid数据slots.push_back({fd, pipefil[1]});}// 阶段二 父进程向子进程传递命令信息// 要想达到子进程接受命令较合理——负载均衡。srand((unsigned)time(0) ^ getpid() * 233333); // 位运算与相乘2333333目的是让数据更随机uint32_t command 0;Taskloading(); // 对方法进行加载while (true){cout ###########################################################\n;cout ## 0. 退出 1. 查看方法表 2. 输入命令 #############\n;cout ###########################################################\n;cout 请输入;cin command;//你往管道里面写数据的逻辑在哪里定位到那里先//子进程正常执行了你的问题是什么 if (command 1 ){print_task();// continue;} else if ( command 2){cout Please imput command : ;cin command;if (!(command 0 command tasklist.size())){cout 无效命令 endl;continue;}size_t n rand() % slots.size(); // 随机数选择子进程ssize_t ret write(slots[n].second, command, sizeof command);if ( ret ! sizeof command){cout write fail endl;}else{cout write success child pid:[ slots[n].first ] 执行 command task_inf[command] endl;}}else if (command 0){cout 退出成功 endl;break;}else{cout 无效命令,请重新输入 endl;}sleep(1);}// 完成分配后关闭管道for (const auto e : slots){close(e.second);}// 回收子进程for (const auto e : slots){waitpid(e.first, nullptr, 0); // 由于auto只遍历一次所以不能用轮询回收}return 0;}
task.hpp
#pragma once
#include iostream
#include assert.h
#include vector
#include string
#include unordered_map
#include functional
#include unistd.husing namespace std;typedef functionvoid() func;vectorfunc tasklist; // 记录方法数
unordered_mapint, string task_inf; // 记录方法信息。void readMySQL()
{cout process : getpid() 访问数据库;
}void val()
{cout process : getpid() 进行数据运算;
}void save()
{cout process : getpid() 进行数据持久化;
}void cal()
{cout process : getpid() 进行数据加密;
}// 加载任务表
void Taskloading()
{task_inf.insert({tasklist.size(), readMySQL});tasklist.push_back(readMySQL);task_inf.insert({tasklist.size(), val});tasklist.push_back(val);task_inf.insert({tasklist.size(), save});tasklist.push_back(save);task_inf.insert({tasklist.size(), cal});tasklist.push_back(cal);cout 方法加载成功 endl;
}// 打印方法表
void print_task()
{for (const auto e : task_inf){cout e.first e.second endl;}
}int tasksize()
{return tasklist.size();
} 我给大家留了个坑不知道大家有没有发现呢 我们在最后的时候调用不了函数 解析 是进程的独立性导致的。 我们知道我们在已经对方法容器进行了加载但是在fork之后父进程的加载父进程进行了实时拷贝子进程的tasklist任然是未加载状态这时你就会问了既然子进程访问的是一个空容器那我们不就是非法访问了吗 是的我们没有看到报错是因为我们在父进程子进程的报错不会导致父进程退出我们通过 查看进程状态表即可发现由于子进程的非法访问导致子进程崩溃成为了僵尸进程。 解决方法 1 fork前进行方法表加载。 2. 每个子进程都加载。 分析父子进程对共享的全局变量都只是只读的权限修改则要写实拷贝因此要么全局变量在fork之前就数据加载好要么在每个子进程都加载一份方法表。如果是我我会一开始就加载好这样就只要保存一份数据。 下一期进程通信基础知识 结语 本小节就到这里了感谢小伙伴的浏览如果有什么建议欢迎在评论区评论如果给小伙伴带来一些收获请留下你的小赞你的点赞和关注将会成为博主创作的动力。