前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >【Linux】进程间通信之管道实现进程池

【Linux】进程间通信之管道实现进程池

作者头像
s-little-monster
发布于 2025-03-07 02:34:46
发布于 2025-03-07 02:34:46
12300
代码可运行
举报
运行总次数:0
代码可运行

一、管道的特点

只能用于具有共同祖先的进程之间进行通信,通常,一个管道由一个进程创建,然后该进程调用fork创建子进程,此后父子进程就可以使用该管道进行通信

管道面向字节流,即管道不晓得自己里面的内容,只是一味按照父子进程之间的协调进行传输信息,父子进程在读取其中的内容时是不看内容是否有\n\0等含有特殊意义的内容

因为管道的本质是一种内存级文件,所以管道的生命周期伴随着进程的退出而结束

一般而言,,内核会对管道操作进行同步与互斥同步是指多个进程或线程在访问共享资源或进行特定操作时,按照一定的顺序或规则进行协调,以确保它们之间的操作能够正确、有序地执行,互斥是指在同一时刻,只允许一个进程或线程访问共享资源,以避免多个进程或线程同时访问导致的数据不一致或冲突问题

管道为半双工通道,只能单向传递信息,需要双向通信就要建立两个管道

我们在命令行中使用的|就是匿名通道

二、进程池

1、概念

我们知道在我们创建子进程的时候要调用fork函数,这是一个系统调用接口,所以会对系统产生成本,如果我们一次创建很多个进程,那么系统会变得很累,所以我们引入池的概念,进程池可以保证在我们需要使用进程的情况下,由于提前创建了子进程,我们直接分配就行了,避免了我们需要大量进程的情况下操作系统很吃力的情况,对提前创建好的这些子进程进行先描述后组织的

2、用管道实现一个简易进程池

(一)头文件、宏、全局变量和main函数
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <iostream>
#include <vector>
#include <string>
#include <unistd.h>
#include "task.hpp"
#include <sys/stat.h>
#include <sys/wait.h>
#include <cstdio>

#define PROCESSNUM 10
std::vector<task_t> tasks;

int main()
{
	//加载任务
	LoadTask(&tasks);
	//定义一个vector管理所有的管道,channel是描述,channels是组织
    std::vector<channel> channels;
    //初始化
    InitProcessPool(&channels);
    //开始进行
    StartProcessPool(channels);
    //清理
    CleanProcessPool(channels);
    return 0;
}
(二)初始化函数InitProcessPool

初始化函数里有一个重要的点就是,我们的子进程是循环创建的,所以在创建第一个子进程时没有问题,但是创建第二个子进程开始,因为刚创建出的第二个子进程与父进程是一样的,此时都作为写端连接着一个管道,我们在图中用绿色的线标注出来了,第三个子进程又可以成为第一二个管道的写端,以此类推,每个子进程后创建的子进程都会是上个信道的写端,这与我们想要父进程写,子进程读的要求相悖,所以我们初始化的另一个目的就是将这些多余的连接全部断开,也就是图中彩色的线全部断开,进而保证只有父进程在写端

task.hpp
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#pragma once

#include <iostream>
#include <vector>
//定义一个函数指针task_t指向返回值为void,没有参数的函数
typedef void (*task_t)();

void task1()
{
    std::cout << "this is task1 running" << std::endl;
}
void task2()
{
    std::cout << "this is task2 running" << std::endl;
}
void task3()
{
    std::cout << "this is task3 running" << std::endl;
}
void task4()
{
    std::cout << "this is task4 running" << std::endl;
}
//加载任务函数,将任务pushback到vector中
void LoadTask(std::vector<task_t> *tasks)
{
    tasks->push_back(task1);
    tasks->push_back(task2);
    tasks->push_back(task3);
    tasks->push_back(task4);
}
test.cpp
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class channel
{
public:
    // 描述父进程的fd,对应子进程的pid,子进程的名字
    channel(int cmdfd, int slaverid, const std::string &processname)
    :_cmdfd(cmdfd),_slaverid(slaverid),_processname(processname)
    {}

    int _cmdfd;
    pid_t _slaverid;
    std::string _processname;
};

void slaver()
{
    while(true)
    {
        // 用于存储从标准输入读取的命令码
        int cmdcode = 0;
        // 从标准输入(管道)读取数据,尝试读取sizeof(int)字节的数据到cmdcode中
        // 如果父进程不给子进程发送数据子进程就会进入阻塞等待
        int n = read(0, &cmdcode, sizeof(int));    
        if(n == sizeof(int))
        {
            // read的返回值与sizeof(int)相等,就输出子进程pid和获得命令码
            // 如果命令码有效就调用task任务,无效就退出
            std::cout <<"slaver say@ get a command: "<< getpid() << " : cmdcode: " 
            <<  cmdcode << std::endl;
            if(cmdcode >= 0 && cmdcode < tasks.size()) tasks[cmdcode]();
        }
        else break;
    }
}

void InitProcessPool(std::vector<channel>* channels)
{
    //用于存储之前创建的管道的写端文件描述符
    //目的是让后续创建的子进程可以关闭这些旧的写端文件描述符,避免资源泄漏
    std::vector<int> oldfds;
    //循环创建子进程
    for(int i = 0; i < PROCESSNUM; i++)
    {
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        if(n < 0)
        {
            return;
        }

        pid_t id = fork();
        if(id < 0)
        {
            return;
        }
        if(id == 0)
        {
            //打印子进程pid,打印并关闭上一个管道写端文件描述符
            std::cout << "child : " << getpid() << " close history fd: ";
            for(auto fd : oldfds)
            {
                std::cout << fd << " ";
                close(fd);
            }
            std::cout << std::endl;
            //关闭写端通道
            close(pipefd[1]);
            //将当前管道的读端文件描述符复制到标准输入
            //这样子进程就可以通过标准输入从管道读取数据
            dup2(pipefd[0],0);
            // 读取完关闭管道读端
            close(pipefd[0]);
            // 子进程主要业务
            slaver();
            //打印子进程要退出了
            std::cout << "process : " << getpid() << " quit" << std::endl;
            exit(0);
        }
        //父进程开始
        //关闭读端
        close(pipefd[0]);
        //将当前channel信息添加到channels进行组织
        std::string name = "process-" + std::to_string(i);
        channels->push_back(channel(pipefd[1],id,name));
        //添加这个写端的文件描述符,方便后面的进程关闭它
        oldfds.push_back(pipefd[1]);
        sleep(1);
    }
}
(三)执行函数StartProcessPool
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//打印一个选择任务的菜单
void Menu()
{
    std::cout << "################################################" << std::endl;
    std::cout << "# 1. 任务一             2. 任务二               #" << std::endl;
    std::cout << "# 3. 任务三             4. 任务四               #" << std::endl;
    std::cout << "#                   0. 退出                     #" << std::endl;
    std::cout << "#################################################" << std::endl;
}

void StartProcessPool(std::vector<channel>* channels)
{
    while(true)
    {	
        int select = 0;
        Menu();
        sleep(1);
        //输入选项
        std::cout << "Please Enter>> ";
        std::cin >> select;

        if(select <= 0 || select >= 5) break;
		//将控制码也就是选择的数字select1234转化为0123,因为vector下标从0开始,所以要-1
        int cmdcode = select - 1;
		//通过管道写入信息,等待slaver()读取
        write(channels[select]._cmdfd, &cmdcode, sizeof(cmdcode));
        sleep(1);
    }
}
(四)清理函数CleanProcessPool
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void CleanProcessPool(std::vector<channel> &channels)
{
    //每个channel对象的左边为父进程的fd,右边为子进程fd,断开父进程fd,然后进程等待
    //父进程断开后子进程会在管道中读到0,即文件结束,然后子进程就会终止
    //然后被父进程回收
    for(const auto &c : channels){
        close(c._cmdfd);
        waitpid(c._slaverid, nullptr, 0);
    }
}

三、进程池其他问题

1、描述整个过程

首先启动进程,将任务函数“上膛”到vector中,然后进行初始化,创建出第一个子进程,第一个子进程执行常规操作,比如将写端关闭,将当前管道读端文件描述符复制到标准输入以来获取标准输入的数据,然后就是等待父进程发送信息,在此同时,父进程也不闲着,将当前读端关闭,然后描述channel进而pushback到channels中进行组织,然后在oldfds中存下管道写端对应的fd,方便后面子进程的断开,然后创建第二个子进程,第二个子进程执行和第一个子进程差不多的操作,唯一的区别就是要将oldfds里面的写端全部断开,然后以此类推

2、细节处理

开始创建第一个子进程并形成管道时,父进程的读端fd==3写端fd==4,到后面就会关闭读端,第二次创建时父进程的读端fd==3写端fd==5,以此类推,父进程的读端将一直为3,而写端递增

创建完成的子进程在父进程发送信息之前都处于阻塞状态,一旦父进程发送信息,比如说上面我们提到的指定某个管道或者指定某个任务

3、标准的制定

一种良好的编程习惯对于一个程序员来说是一件非常好的事情,对于我们main函数中的这三个函数参数,我们发现它们遵守着一定的规则

const &:当我们只进行输入不要输出内容的时候 *:当我们要输出内容的时候,类似于输出型参数 &:当我们既要输入又要输出的时候

今日分享就到这里了~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-03-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【Linux篇】探索进程间通信:如何使用匿名管道构建高效的进程池
进程间通信(IPC, Inter-Process Communication)是指不同进程之间为了交换数据或协调工作所使用的一种机制。在多任务操作系统中,进程是独立的执行实体,通常无法直接访问彼此的内存空间。因此,进程间通信提供了一种让进程之间传递信息、同步操作和共享资源的方式。
熬夜学编程的小王
2025/04/20
1120
【Linux篇】探索进程间通信:如何使用匿名管道构建高效的进程池
【Linux篇章】进程通信黑科技:匿名管道的暗码传递与命名管道的实名使命
本文主要探讨进程间通信中的匿名管道和命名管道。将介绍匿名管道如何凭借其隐蔽性在进程间实现数据传输,以及命名管道因具备可命名的特性所带来的独特通信优势;同时会进行相关示例效果演示;代码编写;带读者一步步理解所谓的匿名管道和命名管道等。
羑悻的小杀马特.
2025/04/22
550
【Linux篇章】进程通信黑科技:匿名管道的暗码传递与命名管道的实名使命
Linux:进程间通信之进程池和日志
        因为每一次我们要进行进程间通信都需要fork,和操作系统做交互是存在很大成本的,所以我们是不是可以提前fork出几个进程,然后当我们想要使用的时候直接去给他们安排任务,这样就减少了系统调用的次数从而提高了内存申请速度!!
小陈在拼命
2024/11/26
1230
Linux:进程间通信之进程池和日志
【Linux】进程间通信:匿名管道与进程池
进程之间需要某种协同,所以如何协同的前提条件就是进程直接需要进行通信,传递有效数据
用户11029103
2025/03/13
1110
【Linux】进程间通信:匿名管道与进程池
【Linux】匿名管道通信场景——进程池
  进程池的实现是依靠匿名管道,通过进程间通信使得父进程能够管理多个进程任务,相当于父进程拥有了很多个进程——进程池,通过不同的进程完成指定的任务。   所以我们需要创建多个匿名管道和子进程,进行进程间通信,发送信息给子进程让它们根据接收到的信息处理相关任务。   因为有多个管道和子进程,为了方便父进程使用不同管道发送对应信息给子进程,我们需要将管道的文件描述符以及对应子进程的pid保存起来,我们选择将它们封装在一个Channel类中。又因为有多个匿名管道和子进程,所以将多个Channel类对象储存在C++STL中的容器vector中来方便父进程进行管理进程池。
大耳朵土土垚
2024/12/02
1200
【Linux】匿名管道通信场景——进程池
【Linux】实现一个简易的进程池
池化技术 (Pool) 是一种很常见的编程技巧,在请求量大时能明显优化应用性能,降低系统频繁建连的资源开销。我们日常工作中常见的有数据库连接池、线程池、对象池等,它们的特点都是将 “昂贵的”、“费时的” 的资源维护在一个特定的 “池子” 中,规定其最小连接数、最大连接数、阻塞队列等配置,方便进行统一管理和复用,通常还会附带一些探活机制、强制回收、监控一类的配套功能。
修修修也
2024/11/21
1030
【Linux】实现一个简易的进程池
匿名管道 Linux
首先自己要用用户层缓冲区,还得把用户层缓冲区拷贝到管道里,(从键盘里输入数据到用户层缓冲区里面),然后用户层缓冲区通过系统调用(write)写到管道里,然后再通过read系统调用,被对方(读端)读取,就要从管道拷贝到读端,然后再显示到显示器上。
ljw695
2024/10/18
1550
匿名管道 Linux
【Linux】进程间通信——进程池
进程池(Process Pool)是一种用于管理进程的技术,它通过预先创建一定数量的进程来避免频繁创建和销毁进程的开销。进程池通常用于需要并发执行大量任务的场景,特别是在处理CPU密集型任务时。
用户11305458
2025/02/19
1040
【Linux】进程间通信——进程池
进程间的通信--管道
进程之间需要协同。 例如,学校里面的各个管理层之间都是互相联系的,不能只是纵向管理。正是因为进程之间需要协同,协同的前提条件是进程之间需要通信,数据是有类别的,有的数据是通知就绪的,有些数据是单纯所传递数据,有的是控制相关的数据。
南桥
2024/07/28
960
进程间的通信--管道
进程间通信Linux
首先自己要用用户层缓冲区,还得把用户层缓冲区拷贝到管道里,(从键盘里输入数据到用户层缓冲区里面),然后用户层缓冲区通过系统调用(write)写到管道里,然后再通过read系统调用,被对方(读端)读取,就要从管道拷贝到读端,然后再显示到显示器上。
ljw695
2024/11/15
1340
进程间通信Linux
【Linux】————进程间通信(匿名管道)
1.一个冷知识: 屏蔽力是一个人最顶级的能力,任何消耗你的人和事,多看一眼都是你的不对。
用户11036582
2024/10/29
1320
【Linux】————进程间通信(匿名管道)
【Linux】进程间通信(匿名管道)
原子的意思就是这次的写入操作不会被中断。写的时候,不会写一半就被读走。在读方看来,要么不写,要么写完了。
秦jh
2024/10/22
1790
【Linux】进程间通信(匿名管道)
匿名管道和命名管道
https://blog.csdn.net/2401_83427936/article/details/142603367
ljw695
2024/10/18
1840
匿名管道和命名管道
【Linux】匿名管道实现简单进程池
        这个进程池可以分配我们想要的进程的个数,用命令行的方式来控制进程的个数,任务由我们自己定好,每次随机选择一个任务指派给一个进程去完成,进程的选派采用轮询的方式按顺序指派,这其中还有一些实现的细节,会在代码中以注释的方式给出。
用户10923276
2024/04/15
1550
【Linux】进程通信实战 —— 进程池项目
进程池是一种用于管理和复用进程的技术,它可以有效地管理系统资源并提高程序的性能和效率。通过维护一组预先创建的进程与管道,进程池可以避免频繁地创建和销毁进程,从而减少了系统开销和资源浪费。
叫我龙翔
2024/05/26
1050
【Linux】进程通信实战 —— 进程池项目
pipe和pipefd
在 Linux 中,pipe 是一个系统调用,用于创建一个管道,这是一种用于进程间通信(IPC)的机制。管道允许两个进程之间进行单向数据传输,通常是一个进程向管道写入数据,而另一个进程从管道读取数据。
ljw695
2024/10/18
1110
pipe和pipefd
Linux进程间通信【匿名管道】
进程间通信简称为 IPC(Interprocess communication),是两个不同进程间进行任务协同的必要基础。进行通信时,首先需要确保不同进程之间构建联系,其次再根据不同的使用场景选择不同的通信解决方案,本文主要介绍的通信解决方案为 匿名管道
北 海
2023/07/01
3470
Linux进程间通信【匿名管道】
【Linux】进程池实现指南:掌控并发编程的核心
今日提到的好事发生的文章是六月的雨在Tencent大佬写的程序员的”恐怖故事“,相信每个程序原都会经历这种时刻吧,当程序写的差不多时客户居然提出要改需求!这个改需求的状态可能三次结束,也可能五次结束,也可能一直不结束...很有趣的文章。文章链接:https://cloud.tencent.com/developer/article/2465509
Yui_
2024/11/13
1400
【Linux】进程池实现指南:掌控并发编程的核心
从零开始:实现进程间管道通信的实例
匿名管道(Anonymous Pipe)是进程间通信(IPC)的一种机制,它主要用于本地父子进程之间的通信。
绝活蛋炒饭
2024/12/16
1760
从零开始:实现进程间管道通信的实例
【Linux进程通信】二、匿名管道
​ 管道是 Unix 中最古老的进程间基于文件系统通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个 “管道”。注意管道是单向连通的,不存在说双向管道,就像生活中水往低处流而不会往高处流一样!
利刃大大
2025/03/15
590
【Linux进程通信】二、匿名管道
相关推荐
【Linux篇】探索进程间通信:如何使用匿名管道构建高效的进程池
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验