【Linux系统】匿名管道 && 模拟实现进程池

进程间通信

进程为什么要通信?

进程也是需要某种协同的,所以如何协同的前提条件,需要通信,数据是有类别的,别人传递的信息对你来说,可能是通知就绪的、单纯要传递给我的数据、控制相关的信息...

事实:进程具有独立性,进程=内核数据结构+代码和数据

创建子进程时各自创建自己的内核数据结构,代码和数据遵守写时拷贝,所以进程是一定独立的!!!

那么该如何通信?

进程如何通信?

1.进程间通信,成本可能会稍微高一些!

2.进程间通信的前提,先让不同的进程,看到同一份(操作系统)资源(“一段内存”)

 a、一定是某一个进程先需要通信,让OS创建一个共享资源

b、OS必须提供很多的系统调用 --- OS创建的共享资源的不同,系统调用接口的不同,进程间通信会有不同的种类!!!

进程通信的常见方式是什么?

system V --- 本地通信

这是我们都要遵守的一个标准,世界上各种专利就有各种标准!!!

方式:1.消息队列 2.共享内存 3.信号量

何不直接复用内核代码直接通信呢?

利用管道:1.匿名管道 2.命名管道

匿名管道

下面我们创建父子两进程!!!

 两进程序都需同时进行读和写的操作,当我们创建子进程时,子进程需不需要再创建一份文件系统呢?不需要,我们说进程是独立的,我们并没有说过文件系统是具有独立性的!!!

所以OS就会让父子进程共用同一份文件系统!!!

理解一种现象:为什么父子进程会向同一个显示器端打印数据!

进程默认会打开标准输入输出0/1/2...怎么做到的?

我们创建的父进程是bash的子进程,bash打开了所有的子进程也就打开了,我们只要做好约定即可!!!与硬链接的引用计数类似,这里也有引用计数!!!所以子进程关闭close()0/1/2,不影响父进程继续使用显示器文件!!!

 我们让父进程关闭写操作,子进程关闭读操作,这样,父进程进行只读,子进程进行写!!!

形成了一种单向的通信 --- 管道

我们再想一个问题,经过缓冲区时需不需要再刷新到磁盘上呢???

不需要,这不是多此一举吗?OS不喜欢做这样的事情,怎么简单怎么来!!!

父子既然要关闭不需要的fd,为什么曾经要打开呢?可以不关闭吗?

理解一个系统调用接口。

 参数为输出型参数!!!

不需要文件路径和文件名,匿名管道

下面创建管道,管道有多大???在Ubuntu 20.04管道大小是64kb

父进程读子进程写

1.父进程创建管道

2.父进程fork出子进程

3.父进程关闭fd[0],子进程关闭fd[1]

父子既然要关闭不需要的fd,为什么曾经要打开呢?可以不关闭吗? 

为什么曾经要打开是因为为了让子进程继承下去!!!

可以不关闭吗?可以,但建议关闭,万一误写了呢!!!

我们一直在做的事情进程间通信,进程间通信是有成本的!!!

进场间通信本质:让不同的进程看到了同一份资源

思考两点:1.如果我想双向通信呢?两个管道

2.为什么要单向通信,因为单向通信简单,只让单向通信,由此产生管道!!!

实现单向通信的测试代码

#include<iostream>
#include <unistd.h>
#include<cerrno> // errno.h
#include<cstring> // string.h
#include<sys/wait.h>
#include<sys/types.h>

//fork之后子进程是能拿到父进程的数据的 --- 通信吗?写时拷贝,对方都看不到
//char buffer[1024];//不行的

const int size = 1024;

std::string getOtherMessage()
{
    static int cnt = 0;
    std::string messageid = std::to_string(cnt);
    cnt++;
    pid_t self_id = getpid();
    std::string stringpid = std::to_string(self_id);
    std::string message = "messageid:";
    message += messageid;
    message += "my pid is:";
    message += stringpid;

    return message;
}

//子进程进行写入
void SubProcessWrite(int wfd)
{
    int pipesize = 0;
    std::string message = "father,I am you son process!";
    char c = 'A';
    while(true)
    {
        std::cout << "++++++++++++++++++++++++++++++++++++++++++++++++" << std::endl;
        std::string info = message + getOtherMessage();//这条消息就是子进程发给父进程的消息
        write(wfd, info.c_str(), info.size());//写入管道的时候,没有写入\0,有没有必要?没有必要!
        std::cerr << info << std::endl;

        sleep(1);//子进程慢点写
        // write(wfd, &c, 1);
        // std::cout << "pipesize:" << ++pipesize << "write charator is:" << c++ << std::endl;
        // if(c == 'G') break;

        // sleep(1);
    }
}

//父进程进行读取
void FatherProcessRead(int rfd)
{
    char inbuffer[size]; //c99后支持
    while(true)
    {
        sleep(2);
        std::cout << "--------------------------------------------" << std::endl;
        ssize_t n = read(rfd, inbuffer, sizeof(inbuffer) - 1);
        if(n > 0)
        {
            inbuffer[n] = 0;//这个位置给为'\0'
            std::cout << inbuffer << std::endl;
        }
        else if(n == 0)
        {
            //如果read的返回值是0,表示写端直接关闭了,我们读到了文件的结尾
            std::cout << "client quit,father get return val:" << n << "father quit too!" << std::endl;
            break;
        }
        else
        {
            std::cerr << "read error" << std::endl;
            break;
        }
        //sleep(1);
        //break;   
    }
}
int main()
{
    //1.创建管道
    int pipefd[2];
    int n = pipe(pipefd);//输出型参数, rfd wfd
    if(n != 0)
    {
        std::cerr << "errno:" << errno << " : " << "errstring: " << strerror(errno) << std::endl;
        return 1;
    }
    //pipefd[0] -> 0 -> r(嘴巴读)  pipefd[1] -> 1 -> w(笔写)
    std::cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << std::endl;
    sleep(1);

    //2.创建子进程
    pid_t id = fork();
    if(id == 0)
    {
        std::cout << "子进程关闭不需要的fd了,准备发消息了" << std::endl;
        // 子进程 -- write
        // 3.关闭不需要的fd
        close(pipefd[0]);
        //if(fork() > 0) exit(0);
        SubProcessWrite(pipefd[1]);


        close(pipefd[1]);
        exit(0);
    }

    std::cout << "父进程关闭不需要的fd了,准备收消息了" << std::endl;
    sleep(1);
    //父进程 --- read
    //3.关闭不需要的fd
    close(pipefd[1]);

    FatherProcessRead(pipefd[0]);
    //std::cout << "5s,father close rfd" << std::endl;
    //sleep(5);
    close(pipefd[0]);
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        std::cout << "wait child process done,exit sig:" << (status&0x7f) << std::endl;
        std::cout << "wait child process done,exit code(ign):" << ((status >> 8)&0xff) << std::endl;
    }
    return 0;
}

管道的4种情况

  1. 如果管道内部是空的 && write fd没有关闭,读取条件不具备,该进程会被阻塞,父进程wait,等待读取条件具备再写入数据。
  2. 如果管道被写满 && read fd不读且没有关闭,管道被写满,写进程会被阻塞(管道被写满,写条件不具备)父进程wait等待写条件具备,读取数据。
  3. 管道一直在读 && 写端关闭了wfd,读端read返回值会读到0,表示读到了文件结尾。
  4. rfd直接关闭,写端wfd一直在进行写入?写端操作会被操作系统直接使用13号信号关掉。相当于进程出现了异常

管道的5种特征

  1. 匿名管道:只用来具有血缘关系的进程之间,进行通信,常用与父子进程之间通信。
  2. 管道内部,自带进程之间同步的机制 -- > 多执行流执行代码的时候,具有明显的顺序性!!
  3. 管道文件的生命周期是随进程的!!!
  4. 管道文件在通信的时候,是面向字节流的。(现阶段不易理解)write的次数和读取的次数不是一一匹配的
  5. 管道的通信方式,是一种特殊的半双工模式(像两个人说话,都可以说话,但只有一方说时另一方就只能听),还有一种叫全双工(像我们日常生活的吵架)。

atomic:原子的 

意思是小于PIPE_BUF时写入是安全的!!! 

Linux下PIPE_BUF是4096字节。

 进程池

由一个父进程通过管道来控制多个子进程父进程写入,子进程读取。

master向哪个管道进行写入 ,就是唤醒哪个子进程执行任务

固定长度的4字节长度的数组下标 --- 任务码

父进程要进行后端任务 划分的负载均衡

轮询方案!!!

如果管道里面没有数据,worker进程就在阻塞等待,等待任务的到来。

 ProcessPool.cc文件

#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"

// master
class Channel
{
public:
    Channel(int wfd, pid_t id, const std::string &name)
        : _wfd(wfd)
        , _subprocessid(id)
        , _name(name)
    {
    }

    int GetWfd() { return _wfd; }
    pid_t GetProcessid() { return _subprocessid; }
    std::string GetName() { return _name; }

    void CloseChannel()
    {
        close(_wfd);
    }
    void Wait()
    {
        pid_t rid = waitpid(_subprocessid, nullptr, 0);
        if (rid > 0)
        {
            std::cout << "wait " << rid << " success" << std::endl;
        }
    }
    ~Channel()
    {
    }

private:
    int _wfd;
    pid_t _subprocessid;
    std::string _name;
};

// 形参类型和命名规范
// const &:输出
//&: 输入输出
//*:输出型参数
// task_t task回调函数
void CreateChannelAndSub(int num, std::vector<Channel> *channels, task_t task)
{
    for (int i = 0; i < num; i++)
    {
        // 1.创建管道
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        if (n < 0)
            exit(1);
        // 2.创建子进程
        pid_t id = fork();
        if (id == 0)
        {
            if(!channels->empty())
            {
                //第二次之后,创建的管道
                for(auto &channel : *channels)
                    channel.CloseChannel();
            }
            // child read
            close(pipefd[1]);
            dup2(pipefd[0], 0); // 将管道的读端重定向到标准输入
            task();
            close(pipefd[0]);
            exit(0);
        }
        // 构建一个channel名字
        std::string channel_name = "Channel-" + std::to_string(i);
        // 父进程
        // father write
        close(pipefd[0]);
        // a. 子进程的pid,b. 父进程关心的管道的w端
        channels->push_back(Channel(pipefd[1], id, channel_name));
    }
}

int NextChannel(int channelnum)
{
    static int next = 0;
    int channel = next;
    next++;
    next %= channelnum;
    return channel;
}
void SendTaskCommand(Channel &channel, int taskcommand)
{
    write(channel.GetWfd(), &taskcommand, sizeof(taskcommand));
}
void ctrlProcessOnce(std::vector<Channel> &channels)
{
    sleep(1);
    // a.选择一个任务
    int taskcommand = SelectTask();
    // b.选择一个信道和进程
    int channel_index = NextChannel(channels.size());
    // c.发送任务
    SendTaskCommand(channels[channel_index], taskcommand);
    std::cout << "taskcommand:" << taskcommand << "channel:" << channels[channel_index].GetName() << "sub process:" << channels[channel_index].GetProcessid() << std::endl;
}
void ctrlProcess(std::vector<Channel> &channels, int times = -1)
{
    if (times > 0)
    {
        while (times--)
        {
            ctrlProcessOnce(channels);
        }
    }
    else
    {
        while (true)
        {
            ctrlProcessOnce(channels);
        }
    }
}
void CleanUpChannel(std::vector<Channel> &channels)
{
    // int num = channels.size() - 1;
    // while(num >= 0)
    // {
    //     channels[num].CloseChannel();
    //     channels[num--].Wait();
    // }

    for (auto &channel : channels)
    {
        channel.CloseChannel();
        channel.Wait();
    }
    // for (auto &channel : channels)
    // {
    //     channel.Wait();
    // }
}
//./processpool 5
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage:" << argv[0] << "processnum" << std::endl;
        return 1;
    }
    int num = std::stoi(argv[1]);
    LoadTask();

    std::vector<Channel> channels;
    // 1.创建信道和子进程
    CreateChannelAndSub(num, &channels, work);
    // 2.通过channel控制子进程
    ctrlProcess(channels, 5);
    // 3.回收管道和子进程 a.关闭所有的写端 b.回收子进程
    CleanUpChannel(channels);
    return 0;
}

Task.hpp文件

#pragma once

#include <iostream>
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>

#define TaskNum 3

typedef void (*task_t)(); // task_t 函数指针类型

void Print()
{
    std::cout << "I am print task" << std::endl;
}
void DownLoad()
{
    std::cout << "I am a download task" << std::endl;
}
void Flush()
{
    std::cout << "I am a flush task" << std::endl;
}

task_t tasks[TaskNum];

void LoadTask()
{
    srand(time(nullptr) ^ getpid() ^ 17777);
    tasks[0] = Print;
    tasks[1] = DownLoad;
    tasks[2] = Flush;
}

void ExcuteTask(int number)
{
    if (number < 0 || number > 2)
        return;
    tasks[number]();
}

int SelectTask()
{
    return rand() % TaskNum;
}


void work()
{
    while(true)
    {
        int command = 0;
        int n = read(0, &command, sizeof(command));
        if(n == sizeof(int))
        {
            std::cout << "pid is:" << getpid << "handler task" << std::endl;
            ExcuteTask(command);
        }
        else if(n == 0)
        {
            std::cout << "sub process:" << getpid << "quit" << std::endl;
            break;
        }
    }
}
  • 29
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花影随风_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值