匿名管道实例--进程池【Linux】

前言

在学习了匿名管道的基本内容之后,你有没有想过自己使用一下匿名管道,本文就详细介绍一个匿名管道的实例–进程池。
匿名管道详解:进程间通信–匿名管道【Linux】

一、实例分析

本实例想通过管道实现一个功能 – 父进程控制一批子进程完成对应的任务

简而言之,就是父进程通过管道给一批子进程分别发送任务代码,然后子进程完成对应的任务,可以分为以下几个步骤。

①加载任务列表
②创建子进程
③父进程派发任务
④回收资源

在这里插入图片描述

二、管道

什么是管道

  • 管道是Unix中最古老的进程间通信的形式。
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”

原理

父进程指向一个管道文件,子进程继承父进程的指向关系,从而子进程也指向管道,进行通信。
在这里插入图片描述

三、实例代码

3.0 加载任务列表

由于我们需要指派子进程完成任务,首先需要一些任务列表,本实例不详细实现任务,只是打印输出执行任务成功,来模拟实现完成任务。

typedef void (*functor)();

一个数组指针,用来保存任务执行的函数。

vector<functor> functors;

一个方法集合,用vector来存放一系列的任务执行的数组指针。

unordered_map<uint32_t, string> info;

存放uint32_t类型的key值以及string字符串,存放任务名称。
其中uint32_tunsighed int的别名,为无符号整形。

生成任务

typedef void (*functor)();

vector<functor> functors; // 方法集合
// for debug
unordered_map<uint32_t, string> info;

void f1()
{
    cout << "这是一个处理日志的任务,执行的进程 ID [" << getpid() << "]"
         << ", 执行时间是 [" << time(nullptr) << "]" << endl;
}
void f2()
{
    cout << "这是一个备份数据的任务,执行的进程 ID [" << getpid() << "]"
         << ", 执行时间是 [" << time(nullptr) << "]" << endl;
}
void f3()
{
    cout << "这是一个处理网络链接的任务,执行的进程 ID [" << getpid() << "]"
         << ", 执行时间是 [" << time(nullptr) << "]" << endl;
}

void loadFunctor()
{
    info.insert({functors.size(), "处理日志的任务"});
    functors.push_back(f1);

    info.insert({functors.size(), "备份数据的任务"});
    functors.push_back(f2);

    info.insert({functors.size(), "处理网络链接的任务"});
    functors.push_back(f3);
}

生成任务之后,在main函数中使用loadFunctor函数来加载任务列表。

3.1 创建子进程

创建五个子进程,并连接所对应的管道,父进程保存管道相关信息。

typedef pair<int32_t, int32_t> elem;
vector<elem> assignMap;

int32_t: 进程pid,int32_t:该进程对应的管道写端fd。
定义了一个名为 assignMap 的 vector,它的元素类型是 elem,也就是一对 int32_t 类型的值。用来建立子进程和管道的连接。

// int32_t: 进程pid,int32_t:该进程对应的管道写端fd
typedef pair<int32_t, int32_t> elem;
int processNum = 5;

void createChildProcess(vector<elem> &assignMap)
{
    // 创建procesNum个进程
    for (int i = 0; i < processNum; i++)
    {
        // 定义保存管道fd的对象
        int pipefd[2] = {0};
        // 创建管道
        pipe(pipefd);
        // 创建子进程
        pid_t id = fork();
        if (id == 0)
        {
            // 子进程读取,r -> pipefd[0]
            close(pipefd[1]);
            // 子进程执行
            work(pipefd[0]);
            close(pipefd[0]);
            exit(0);
        }
        // 父进程做的事情 -> pipefd[1]
        close(pipefd[0]);
        elem e(id, pipefd[1]);
        assignMap.push_back(e);
    }
}

子进程核心工作

子进程执行以下三个操作

  • 阻塞等待管道中的任务信息
  • 获取任务信息
  • 处理任务信息
assert(s == sizeof(uint32_t));
(void)s;

assert断言,是编译有效 (debug 模式),在release 模式下,断言就失效了。一旦断言失效,s变量就是只被定义,没有被使用,release模式中,可能会有warning。

void work(int blockFd)
{
    cout << "进程[" << getpid() << "]" << " 开始工作" << endl;
    // 子进程核心工作的代码
    while (true)
    {
        // a.阻塞等待 b.获取任务信息
        uint32_t operatorCode = 0;
        ssize_t s = read(blockFd, &operatorCode, sizeof(uint32_t));
        if (s == 0) break;
        assert(s == sizeof(uint32_t));
        (void)s;

        // c.处理任务
        if (operatorCode < functors.size()) functors[operatorCode]();
    }
    cout << "进程[" << getpid() << "]" << " 结束工作" << endl;
}

3.2 父进程派发任务

父进程执行以下四个操作

  • 随机选择一个子进程
  • 随机选择一个任务
  • 将任务分配给指定的子进程
  • 打印任务相关信息
void blanceSendTask(const vector<elem> &processFds)
{
    srand((long long)time(nullptr));
    while(true)
    {
        sleep(1);
        // 选择一个进程
        uint32_t pick = rand() % processFds.size();

        // 选择一个任务
        uint32_t task = rand() % functors.size();

        // 把任务给一个指定的进程
        write(processFds[pick].second, &task, sizeof(task));

        // 打印对应的提示信息
        cout << "父进程指派任务->" << info[task] 
        << "给进程:" << processFds[pick].first << " 编号:" << pick << endl;
    }
}

3.3 回收资源

当子进程停止工作时,父进程等待子进程结束回收资源,并关闭管道。

void recycleResources(const vector<elem> &assignMap)
{
    for (int i = 0; i < processNum; i++)
    {
        if (waitpid(assignMap[i].first, nullptr, 0) > 0)
            cout << "wait for: pid = " << assignMap[i].first
                 << " number: " << i << "wait sussess!" << endl;
        close(assignMap[i].second);
    }
}

3.4 主函数

int main()
{
    vector<elem> assignMap;
    // 加载任务列表
    loadFunctor();
    // 创建子进程
    createChildProcess(assignMap);
    cout << "create all process success!" << endl;
    // 父进程,派发任务
    blanceSendTask(assignMap);
    // 回收资源
    recycleResources(assignMap);
    return 0;
}

输出样例

左图:监控脚本,监控进程信息

while :; do ps axj | head -1; ps axj | grep mypipe | grep -v grep; sleep 1;done

右图:代码执行信息

在这里插入图片描述
可以看到创建了父进程和五个子进程,子进程开始工作,并且父进程随机给五个子进程分配随机任务。

源代码

#include <iostream>
#include <unistd.h>
#include <vector>
#include <unordered_map>
#include <string>
#include <cstring>
#include <ctime>
#include <cassert>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;

typedef void (*functor)();
vector<functor> functors; // 方法集合
// for debug
unordered_map<uint32_t, string> info;

void f1()
{
    cout << "这是一个处理日志的任务,执行的进程ID [" << getpid() << "]"
         << ", 执行时间是 [" << time(nullptr) << "]" << endl << endl;
}
void f2()
{
    cout << "这是一个备份数据的任务,执行的进程ID [" << getpid() << "]"
         << ", 执行时间是 [" << time(nullptr) << "]" << endl << endl;
}
void f3()
{
    cout << "这是一个处理网络链接的任务,执行的进程ID [" << getpid() << "]"
         << ", 执行时间是 [" << time(nullptr) << "]" << endl << endl;
}

void loadFunctor()
{
    info.insert({functors.size(), "处理日志的任务"});
    functors.push_back(f1);

    info.insert({functors.size(), "备份数据的任务"});
    functors.push_back(f2);

    info.insert({functors.size(), "处理网络链接的任务"});
    functors.push_back(f3);
}

// int32_t: 进程pid,int32_t:该进程对应的管道写端fd
typedef pair<int32_t, int32_t> elem;
int processNum = 5;

void work(int blockFd)
{
    cout << "进程[" << getpid() << "]" << " 开始工作" << endl;
    // 子进程核心工作的代码
    while (true)
    {
        // a.阻塞等待 b.获取任务信息
        uint32_t operatorCode = 0;
        ssize_t s = read(blockFd, &operatorCode, sizeof(uint32_t));
        if (s == 0) break;
        assert(s == sizeof(uint32_t));
        (void)s;

        // c.处理任务
        if (operatorCode < functors.size()) functors[operatorCode]();
    }
    cout << "进程[" << getpid() << "]" << " 结束工作" << endl;
}

void createChildProcess(vector<elem> &assignMap)
{
    // 创建procesNum个进程
    for (int i = 0; i < processNum; i++)
    {
        // 定义保存管道fd的对象
        int pipefd[2] = {0};
        // 创建管道
        pipe(pipefd);
        // 创建子进程
        pid_t id = fork();
        if (id == 0)
        {
            // 子进程读取,r -> pipefd[0]
            close(pipefd[1]);
            // 子进程执行
            work(pipefd[0]);
            close(pipefd[0]);
            exit(0);
        }
        // 父进程做的事情 -> pipefd[1]
        close(pipefd[0]);
        elem e(id, pipefd[1]);
        assignMap.push_back(e);
    }
}

// [子进程的pid,子进程的管道fd]
void blanceSendTask(const vector<elem> &processFds)
{
    srand((long long)time(nullptr));
    while(true)
    {
        sleep(1);
        // 选择一个进程
        uint32_t pick = rand() % processFds.size();

        // 选择一个任务
        uint32_t task = rand() % functors.size();

        // 把任务给一个指定的进程
        write(processFds[pick].second, &task, sizeof(task));

        // 打印对应的提示信息
        cout << "父进程指派任务->" << info[task] 
        << "给进程:" << processFds[pick].first << " 编号:" << pick << endl;
    }
}

void recycleResources(const vector<elem> &assignMap)
{
    for (int i = 0; i < processNum; i++)
    {
        if (waitpid(assignMap[i].first, nullptr, 0) > 0)
            cout << "wait for: pid = " << assignMap[i].first
                 << " number: " << i << "wait sussess!" << endl;
        close(assignMap[i].second);
    }
}

int main()
{
    vector<elem> assignMap;
    // 加载任务列表
    loadFunctor();
    // 创建子进程
    createChildProcess(assignMap);
    cout << "create all process success!" << endl;
    // 父进程,派发任务
    blanceSendTask(assignMap);
    // 回收资源
    recycleResources(assignMap);
    return 0;
}

总结

本文从代码角度详细介绍了一个匿名管道的实例–进程池,功能是父进程控制一批子进程完成对应的任务。大家有兴趣的话可以尝试用自己的方式实现这个功能。喜欢的话,欢迎点赞支持和关注~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值