Linux 24 进程通信及管道(附上源码实现)

🔥个人主页: Milestone-里程碑

❄️个人专栏: <<力扣hot100>> <<C++>><<Linux>>

<<Git>><<MySQL>>

🌟心向往之行必能至

目录

一.前言

[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.倒着关:倒着关,每个管道对应的写端都能够被成功关闭,读端也就关闭

bash 复制代码
 void 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.在创建子进程时,将子进程继承到的写端关闭

第一次为空,每次都是直到上一次父进程打开的文件描述符(虽然本次父进程也会打开插入,但会发生写时拷贝,对子进程无影响)

bash 复制代码
 void CloseAll()
    {
        for(auto &ch:channel)
        {
            ch.Close();
            ch.Wait();
        }
    }
bash 复制代码
  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]: 写端
                _chmger.CloseAll();
                close(pipefds[1]);
                work(pipefds[0]);
                close(pipefds[0]);
                exit(0);
            }
            //father
            else{
                close(pipefds[0]);
                _chmger.Insert(pipefds[1],id);
            }

        }
    }
相关推荐
安科士andxe5 小时前
深入解析|安科士1.25G CWDM SFP光模块核心技术,破解中长距离传输痛点
服务器·网络·5g
春日见8 小时前
拉取与合并:如何让个人分支既包含你昨天的修改,也包含 develop 最新更新
大数据·人工智能·深度学习·elasticsearch·搜索引擎
寻寻觅觅☆8 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
偷吃的耗子8 小时前
【CNN算法理解】:三、AlexNet 训练模块(附代码)
深度学习·算法·cnn
小白同学_C8 小时前
Lab4-Lab: traps && MIT6.1810操作系统工程【持续更新】 _
linux·c/c++·操作系统os
今天只学一颗糖8 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构
2601_949146538 小时前
Shell语音通知接口使用指南:运维自动化中的语音告警集成方案
运维·自动化
儒雅的晴天9 小时前
大模型幻觉问题
运维·服务器
化学在逃硬闯CS9 小时前
Leetcode1382. 将二叉搜索树变平衡
数据结构·算法
ceclar1239 小时前
C++使用format
开发语言·c++·算法