Linux之进程间通信(二)---模拟进程池

Linux之进程间通信(二)---模拟进程池

进程池

创建多个子进程

只有一个父进程一个子进程

外层套循环创建多个进程

父进程还要对channle进行先描述在组织

对管道进行描述->class Channel


大纲

1.创建多个子进程

2.对子进程和管道进行管理

3.释放资源


阶段检测是否创建子进程成功和文件描述符


接下来对现有函数进行封装--利用c++类

class ProcessPool { public: // a. 创建多个进程,创建多个管道 int CreateProcessPool() { for (int i = 0; i < 5; i++) { int pipefd[2] = {0}; int n = pipe(pipefd); if (n < 0) { cerr << "pipe error" << endl; return pipeerror; } pid_t id = fork(); if (id < 0) { cerr << "fork error" << endl; return forkerror; } else if (id == 0) { // 子进程 close(pipefd[1]); // 子进程关掉写,进行读 DoTask(pipefd[0]); exit(OK); } else { // 父进程 close(pipefd[0]); // 父进程写 // pipefd[1]循环一下就没有了,要储存 Channel ch(pipefd[1], id); // 将子进程和要写的管道文件进行记录 channels.push_back(ch); cout << " 创建子进程成功,pid:" << id << endl; } } return OK; } void DebugPrint() { for(auto &ch : channels) { ch.Print(); } } private: // 未来维护和组织所有的channel vector<Channel> channels; };

将创建多个子进程,和打印,文件描述符,创建子进程是否成功都封装到ProcessPool这个类里面,然后将vector<Channel> channels 变成该类的成员变量,main函数的内容变得十分简洁

实现完成不同任务的需求

约定统一四字节进行读写

任务我们想要不要让他很重复,这个时候要用到函数指针,然后回调的操作来实现

  • function<void(int)>可调用对象包装器
    • void:函数返回值为 void
    • (int):这个可调用对象接收 1 个 int 类型参数

task_t就是别名,代表:所有可以接收 int 参数、返回 void 的可调用类型

可以包装:普通函数、函数指针、lambda 表达式、仿函数对象。

using task_t=function<void(int)>;

task_t是类型别名,代表:一个可调用对象,接收 1 个 int 参数,返回 void


void CreateProcessPool(task_t cb)
  • cb:callback 回调,类型是task_t
  • 意思:函数CreateProcessPool接收一个可调用对象进来,不在本函数内部执行,交给子进程去调用

//子进程分支 close(pipefd[1]); cb(pipefd[0]); // 执行回调! exit(OK);

子进程拿到管道读端pipefd[0],把文件描述符当做实参传给回调,执行用户传入的函数。


pool.Init(DoTask);

DoTask函数传进去。DoTask签名:void DoTask(int fd),正好匹配void(int)

整体流程:main 把 DoTask 作为回调传给 Init → Init 传给 CreateProcessPool → fork 出子进程 → 子进程调用cb(pipefd[0]),等价执行DoTask(pipefd[0])


对子进程和管道进行管理

那么要如何选择一个进程呢???

比较好的是将任务平均分配到子进程

1.轮询

2.随机

3.权重

接下来我们采用轮询的方式来分配任务

选择任务

制作任务清单

任务表实现的底层

typedef 干了什么

typedef void(*mytask_list)();

  • void (*)():这是函数指针类型,代表:保存一个函数地址,该函数没有参数,返回值 void。
  • typedef:给这个复杂类型起一个简短名字:mytask_list
// 普通:int a; a是int类型变量 // 现在:mytask_list fp; fp就是一个函数指针变量,可以存函数地址 mytask_list fp = SyncDick; fp(); //等价 SyncDick();

mytask_list mytask[4];

  • mytask[4]:这是函数指针数组,数组里面每一格,都能存放一个函数的地址。
  • {SyncDick, Download, PrintLog, UpdateStatus}:把 4 个全局函数的地址,依次填进数组 0、1、2、3 下标位置。
    数组下标存储的函数地址调用写法
    0SyncDickmytask[0]();→执行 SyncDick ()
    1Downloadmytask[1]();→执行 Download ()
    2PrintLogmytask[2]();→执行 PrintLog ()
    3UpdateStatusmytask[3]();→执行 UpdateStatus ()

种下随机数种子^pid,让取出来的数更加随机

验证任务分配是否随机

发送任务给进程

processpool

Channel补充写和管道名的成员函数

子进程完成发送过来的任务

释放资源

processpool创建成员函数Quit用来释放资源

要将管道关闭

然后将子进程进行回收

问题一

如果父进程关闭,但是子进程还没有跑完会有影响吗

是没有的,子进程只有将管道内容全部读完才会结束

问题二

将Quit这样可以吗

发现卡住了

那么这是为什么呢???

实际上是因为管道文件没有被关干净

子进程是以父进程为模板创建的,很多的内核数据结构的信息都是和父进程一样的

也就是说,越到后面创建的子进程对于前面父进程创造的管道文件都拥有写的权限

将父进程对于管道文件的写权限关闭,其管道文件对应的子进程并不能接受到管道以全部关闭,然后再退出,因为这其中后面创建的子进程对于改管道文件依然可以写入

那么应该要如何解决这个问题呢???

解决方案:

1.从最后一个创建的子进程开始关闭管道文件

2.实现真正w:r=1:1

让之后创建的子进程关闭之前的管道文件