Loading [MathJax]/jax/output/CommonHTML/config.js
部署DeepSeek模型,进群交流最in玩法!
立即加群
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >【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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
使用微搭低代码一键搭建企业微官网,网站建设如此简单
日常中我们无论是网上购物还是到店消费,如果企业有个官网,无疑可以方便顾客了解企业信息。利用微信小程序宣传企业是一个便捷的渠道,可以直接使用,也可以收藏到常用小程序里。今天作者就给大家介绍一款快速搭建企业官网的低代码开发工具,让您在很少的时间很快就能讲网站搭建出来。
低代码布道师
2021/07/30
2K0
小程序实现双列布局
双列布局的话特点是随着数据的增多,我们会出现偶数或者奇数的问题,如果是偶数本身已经填满了,问题不大。主要存在奇数的问题,比如我们如果有3条数据,预期实现的效果
低代码布道师
2023/10/11
3830
微搭低代码从入门到精通12-网格布局
开发小程序首要的就是考虑布局的问题,我们在以前的版本只能选择普通容器结合图片和文本组件来构建页面。
低代码布道师
2023/02/16
7100
最佳实战|如何使用腾讯云微搭从0到1开发企业门户应用
在创建应用之前,我们需要知道,当应用中存在使用动态数据功能模块时,便需要创建对应的数据模型进行管理。以企业门户应用为例,我们需要创建的数据模型以及字段如下:
腾讯云开发TCB
2022/02/22
2.7K2
最佳实战|如何使用腾讯云微搭从0到1开发企业门户应用
创建水平滚动的正确方式【CSS 网格布局】
自从奈飞 Netflix 成为了家喻户晓的名字以来,在移动端中我们一直使用横向布局。水平滚动容器(列表)已经成为了一种常见的布局做法,而不是将东西都堆叠在页面上,这将减少占用小屏幕设备垂直的空间。
Jimmy_is_jimmy
2022/11/22
2.7K0
创建水平滚动的正确方式【CSS 网格布局】
微搭低代码官方模板解析(一)
微搭低代码目前开放了PC端功能,使用方法是需要在模板中心启用模板,模板创建成功后会自动增加PC端的组件库及创建需要的数据源。本文就结合目前官方提供的模板,按照示例程序自己搭建一遍。通过模板的搭建来熟悉官方组件库的用法。
腾讯云开发TCB
2021/07/28
1.4K0
微搭低代码官方模板解析(一)
会员管理实战教程10-布局介绍
低码工具更侧重于前端开发,学习前端开发必须熟练掌握CSS的相关知识。本篇就重点介绍一下在低码中的布局相关知识。
韩锴
2022/02/24
8240
电商小程序实战教程-首页的创建
我们已经利用三篇的内容介绍了电商小程序的总体规划、数据源的开发及管理后台的开发。有了这三个基础就相当于打好了地基,之后就是在地基上按照图纸进行施工了。
韩锴
2022/03/04
7750
微搭低代码从入门到精通07-基础布局组件
低码开发不同于传统开发,传统开发我们通常需要编写前端代码和后端代码。前端代码由HTML、CSS和JavaScript组成,后端代码我们通常要用后端语言比如Java来编写接口。
低代码布道师
2023/02/16
5320
微搭低代码从入门到精通09-数据容器
微搭中还有粗粒度的组件,今天介绍的数据容器就是粗粒度的组件。所谓粗粒度的组件,一般包括基础组件、样式还有默认的事件。数据容器一共包含三种分别是数据列表、数据详情和表单容器。
低代码布道师
2023/02/16
5800
CSS Flexbox 布局完全指南 (一):4000字,多示例讲解
理解 Flexbox 最好的方式是什么?学好基础,再大量练习。这正是我们要在这篇文章中做的事情。
一只图雀
2020/07/16
4.7K0
CSS Flexbox 布局完全指南 (一):4000字,多示例讲解
低代码如何构建响应式布局前端页面
在进行项目交付的场景中,常常会存在项目系统在不同设备,不同屏幕尺寸下使用和展示。因此在开发过程中需要针对此场景做针对性处理。一般来说,在处理这样的问题时,我们需要开发和提供不同的布局,通过检测视口的分辨率,判断当前访问设备的种类,请求不同的页面布局从而提供尺寸较为合适的展示场景。而不同的布局,可以选择提前开发完成,或者采用判断窗口大小的方式动态地调整最终页面来实现效果,业内称之为页面的响应式布局。
葡萄城控件
2023/01/05
4.1K0
低代码如何构建响应式布局前端页面
利用微搭低代码开发每周菜谱小程序(一)
既要求有前端开发技能,还得学习小程序的开发语言,如wxml、wxss等,框架、组件、api一套学习下来就感觉门槛很高了。
低代码布道师
2021/07/14
1.9K0
利用微搭低代码开发每周菜谱小程序(一)
微搭低代码样式开发-盒模型介绍
要想掌握好样式开发,一些样式里的基本概念是必须要了解和掌握的。在布局中比较重要的一个概念就是盒模型,MDN中有详细的介绍盒模型的概念。
低代码布道师
2021/08/11
5370
微搭人员招聘管理系统官方模板解析(一)
微搭目前开放了PC端功能,使用方法是需要在模板中心启用模板,模板创建成功后会自动增加PC端的组件库及创建需要的数据源。本文就结合目前官方提供的模板,按照示例程序自己搭建一遍。通过模板的搭建来熟悉官方组件库的用法。
低代码布道师
2021/07/26
7390
CSS 实用新特性:交互、组件、效率的革新
本文将盘点与解析 CSS 的实用新特性,结合技术原理、应用场景和开发实践。从重塑交互体验,到强化组件功能,再到全面优化开发流程,这些新特性不仅显著提升了开发者的工作效率,还为用户带来了更加流畅、互动性更强的网页体验。
球球的前端奶茶屋
2025/04/09
1070
CSS 实用新特性:交互、组件、效率的革新
微搭低代码实现表单打印功能
在我们的日常开发场景中,表单打印是一个比较常见的场景,微搭本身不带打印功能,我们需要借助一个第三方的库来实现打印。
低代码布道师
2023/10/11
3982
在线预约小程序搭建教程5-科目导航页的制作
布局样式选择flex布局,按照垂直居中的样式排列元素。高度和宽度都可以设置为100%充满容器
低代码布道师
2021/12/27
7800
Uniapp 制作一个横向滚动的工具栏
今日推荐:Spring AI再更新:如何借助全局参数实现智能数据库操作与个性化待办管理
繁依Fanyi
2024/11/12
4970
超级实用!,掌握这9个鲜为人知的CSS属性
虽然许多开发人员熟悉常用的CSS属性,但也有一些较为陌生的属性可能被忽视了。在本文中,我们将探讨10个你可能没有使用过的CSS属性。
前端小智@大迁世界
2023/08/16
5750
推荐阅读
相关推荐
使用微搭低代码一键搭建企业微官网,网站建设如此简单
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验