🔥个人主页: Milestone-里程碑
❄️个人专栏: <<力扣hot100>> <<C++>><<Linux>>
🌟心向往之行必能至
目录
[1.1 为什么要进程通信](#1.1 为什么要进程通信)
[1.2 怎么通信](#1.2 怎么通信)
[1.3 具体通信方式](#1.3 具体通信方式)
[1.3.1 管道](#1.3.1 管道)
[2.1 父子进程如何通过匿名管道通信](#2.1 父子进程如何通过匿名管道通信)
[2.2 代码测试管道](#2.2 代码测试管道)
[2.2.1 5个特性](#2.2.1 5个特性)
[2.2.3 进程池的创建](#2.2.3 进程池的创建)
一.前言
1.1 为什么要进程通信
数据传输:⼀个进程需要将它的数据发送给另⼀个进程
资源共享:多个进程之间共享同样的资源。
通知事件:⼀个进程需要向另⼀个或⼀组进程发送消息,通知它(它们)发⽣了某种事件(如进
程终⽌时要通知⽗进程)。
进程控制:有些进程希望完全控制另⼀个进程的执⾏(如Debug进程),此时控制进程希望能够
拦截另⼀个进程的所有陷⼊和异常,并能够及时知道它的状态改变。
1.2 怎么通信
进程间通信的本质:是先让不同的进程,先看到同一份资源(即内存),然后才有了通信的条件!!!
因此这份资源肯定不是任何一个进程提供的,不让不会看到同一份(改了会写时拷贝),且一个进程访问另一个进程的资源也是非法的
那么该资源就是由OS通过系统调用提供
1.3 具体通信方式
管道
System V进程间通信
POSIX进程间通信
1.3.1 管道
匿名管道pipe
命名管道
二.匿名管道
匿名管道通常使用在父子进程上
2.1 父子进程如何通过匿名管道通信

父进程先创建出管道
父进程再通过fork,创建子进程(前面已说,会继承文件标识表)
然后再将父进程的读端和子进程的写端关闭,实现单向通信
2.2 代码测试管道
我们让子进程写,父进程读1
bash
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
void childwrite(int wfd)
{
char ch=0;
int cnt=10;
while(cnt--)
{
write(wfd,&ch,1);
}
}
void fatherread(int rfd)
{
char buffer[1024];
while(true)
{
buffer[0]={0};
ssize_t n=read(rfd,buffer,sizeof(buffer)-1);
if(n > 0)
{
buffer[n] = 0;
std::cout << "child say: " << buffer<< std::endl;
// sleep(2);
}
else if(n == 0)
{
std::cout << "n : " << n << std::endl;
std::cout << "child 退出,我也退出";
break;
}
else
{
break;
}
}
}
int main()
{
// 1. 创建管道 // fds[0]:读端 fds[1]: 写端
int fds[2]={0};
int n=pipe(fds);
if(n<0)
{
cerr<<"pipe perror"<<endl;
}
// 2. 创建子进程
pid_t id=fork();
if(id ==0)
{
//child
// 3. 关闭不需要的读端,形成通信信道
close(fds[0]);
childwrite(fds[1]);
exit(0);
}
//father
// 3. 关闭不需要的q读写端,形成通信信道
close(fds[1]);
fatherread(fds[0]);
close(fds[0]);
int status = 0;
int ret=waitpid(id,&status,0);
if(ret>0)
{
printf("exit code: %d, exit signal: %d\n", (status>>8)&0xFF, status&0x7F);
sleep(5);
}
return 0;
}
2.2.1 5个特性
1.匿名管道只能用来进行具有血缘关系的进程间进行通信(如父子)
2.管道文件,自带同步机制:
同步机制
写慢 读快,读阻塞
写快,读慢,写慢了,写阻塞
写关,读到末尾也关
读关,也无意义,直接关
3.字节流传输
4,单向通信
:只能一个发,一个接
任何一个时刻,一个发,一个接--半双工
任何一个时刻,可以同时发收--全双工
5.生命周期随进程
2.2.3 进程池的创建
我们分4个块
| 类 / 模块 | 核心作用 |
|---|---|
Channel |
封装 "管道写端 fd + 子进程 pid",提供任务发送、管道关闭、子进程回收等接口 |
ChannelManager |
管理所有Channel对象,实现子进程的负载均衡选择(轮询)、批量关闭 / 回收 |
ProcessPool |
进程池核心类:创建子进程 + 管道、注册任务、分发任务、启停子进程 |
taskManager |
(隐含)注册任务函数、生成任务码、执行指定任务(如打印日志、下载、上传) |
task.hpp
bash
#pragma once
#include <iostream>
#include <vector>
#include <ctime>
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));
}
void Register(task_t t)
{
_tasks.push_back(t);
}
int Code()
{
return rand() % _tasks.size();
}
void Execute(int code)
{
if(code >= 0 && code < _tasks.size())
{
_tasks[code]();
}
}
~TaskManager()
{}
private:
std::vector<task_t> _tasks;
};
processpool.hpp
bash
#pragma once
#include <iostream>
#include <cstdlib> // stdlib.h stdio.h -> cstdlib cstdio
#include <vector>
#include <unistd.h>
#include <sys/wait.h>
//#include<time>
#include "task.hpp"
using namespace std;
//先描述
class Channel{
public:
Channel(int fd,pid_t id)
:_wfd(fd)
,_subid(id)
{
_name="channel"+to_string(fd)+to_string(id);
}
~Channel(){}
void Send(int taskcode)
{
int n= write(_wfd,&taskcode,sizeof(taskcode));
}
void Close(){
close(_wfd);
}
void Wait()
{
pid_t x=waitpid(_subid,nullptr,0);
(void)x;
}
int fd()
{
return _wfd;
}
int subid()
{
return _subid;
}
string name(){return _name;}
private:
int _wfd;
pid_t _subid;
string _name;
};
//再组织
class ChannelManger{
public:
ChannelManger()
:_next(0)
{}
void Insert(int wfd,int childfd)
{
channel.emplace_back(wfd,childfd);
}
Channel& Select()
{
auto&c= channel[_next];
++_next;
_next%=5;
return c;
}
void closewrd()
{
for (auto &ch : channel)
{
std::cout << ch.name() << std::endl;
ch.Close();
}
}
void waitsubid()
{
for(auto&ch:channel)
{
ch.Wait();
}
}
~ChannelManger(){}
private:
int _next;
vector<Channel> channel;
};
class Processpool{
void work(int rfd)
{
while(true)
{
int code=0;
ssize_t n =read(rfd,&code,sizeof(code));
if(n>0)
{
if(n!=sizeof(code))
continue;
cout<<"子进程收到一个任务码:"<<code<<endl;
}
else if(n==0)
{
cout<<"子进程退出"<<endl;
break;
}
else{
cout<<"读取错误"<<endl;
break;
}
}
}
bool start()
{
for(int i=0;i<5;++i)
{
int pipefds[2]={0};
int n =pipe(pipefds);
if(n<0) return false;
pid_t id =fork();
if(id<0) return false;
else if(id==0)
{
// fds[0]:读端 fds[1]: 写端
close(pipefds[1]);
work(pipefds[0]);
close(pipefds[0]);
exit(0);
}
//father
else{
close(pipefds[0]);
_chmger.Insert(pipefds[1],id);
}
}
}
void run()
{
// 1. 选择一个任务
int taskcode = _tm.Code();
// 2. 选择一个信道[子进程],负载均衡的选择一个子进程,完成任务
auto &c =_chmger.Select();
cout << "选择了一个子进程: " << c.name() << endl;
// 2. 发送任务
c.Send(taskcode);
cout<<"发送了一个任务"<<endl;
}
private:
ChannelManger _chmger;
TaskManager _tm;
};

先描述一个信道,再对多个信道进行管理
父进程没有结束,但管道为空时,子进程read读取就会成堵塞状态,直到父进程写入数据
但其实上面的代码关闭与写入是有问题的:
bash
void closeandwait()
{
for(auto&ch:channel)
{
ch.Close();
ch.Wait();
}
}
不信我们同时关闭和写入
原因就出现在父子进程在任一一方没有发送改变之前,会共用资源
那么就会出现这种情况,越先打开的w的fd,会被后面越多的子进程继承,而我们按照打开的顺序关,关第i个,还剩n-i个没关,而写端不关,读端也不会关,而是一直阻塞
解决办法
1.倒着关:倒着关,每个管道对应的写端都能够被成功关闭,读端也就关闭
bashvoid closeandwait() { // for(auto&ch:channel) // { // ch.Close(); // ch.Wait(); // } //1. 倒着关 for(int i=channel.size();i>=0;--i) { channel[i].Close(); channel[i].Wait(); } // }2.在创建子进程时,将子进程继承到的写端关闭
第一次为空,每次都是直到上一次父进程打开的文件描述符(虽然本次父进程也会打开插入,但会发生写时拷贝,对子进程无影响)
bashvoid CloseAll() { for(auto &ch:channel) { ch.Close(); ch.Wait(); } }
bashbool start() { for(int i=0;i<5;++i) { int pipefds[2]={0}; int n =pipe(pipefds); if(n<0) return false; pid_t id =fork(); if(id<0) return false; else if(id==0) { // fds[0]:读端 fds[1]: 写端 _chmger.CloseAll(); close(pipefds[1]); work(pipefds[0]); close(pipefds[0]); exit(0); } //father else{ close(pipefds[0]); _chmger.Insert(pipefds[1],id); } } }

然后再将父进程的读端和子进程的写端关闭,实现单向通信