展开菜单
首页 精品内容 本月促销 装机必备 Windows PDF教程 macOS软件 IOS软件 Android AI 专题
全部分类

当前位置:

首页 > 编程开发 > C++实现进程池:主从架构、管道通信与任务调度(示例详解)

C++实现进程池:主从架构、管道通信与任务调度(示例详解)

主/从进程池架构 在现代高并发系统的设计中,进程池是一个非常经典且高效的模型。它的核心思想说起来并不复杂:用有限数量的子进程,去处理源源不断到来的任务,从而避免频繁的进程创建和销毁开销。这里要聊的,就是基于这种思路的一个典型实现——主/从进程池架构。 一、原理 1 个主进程(Master)+ 预先创

本文目录

    主/从进程池架构

    在现代高并发系统的设计中,进程池是一个非常经典且高效的模型。它的核心思想说起来并不复杂:用有限数量的子进程,去处理源源不断到来的任务,从而避免频繁的进程创建和销毁开销。这里要聊的,就是基于这种思路的一个典型实现——主/从进程池架构。

    一、原理

    1 个主进程(Master)+ 预先创建 N 个子进程(Worker),这就是整个架构的基础。主进程的角色更像是一个调度中心,它不去处理具体的业务,只管把任务分配下去。而子进程,就是真正干活的工人,它们提前创建好,阻塞等待任务,处理完一个再等着下一个。它们之间通过管道来通信。

    这么设计的最大好处是什么?高并发、稳定性强,而且避免了反复创建和销毁进程带来的资源损耗。

    二、三大核心角色

    1. Master(主进程)

    这个角色是唯一的,它的职责非常纯粹——不处理任何业务,只做四件事:

    • 创建进程池:通过 fork 系统调用,一次性创建好一批子进程。
    • 接收任务:从外部或内部获取待处理的任务。
    • 分发任务:将任务分配给当前空闲的子进程。
    • 管理子进程:监控子进程的运行状态,必要时重启、回收。

    2. Worker(子进程 / 进程池)

    这些子进程是预先创建好 N 个的,它们一直活着,不会随意退出。它们的运行逻辑可以简单理解为:阻塞等待任务、收到任务、处理任务、继续等待。整个过程是一个循环。

    3. 管道(通信通道)

    实现父子进程通信的关键。每个子进程都有一条专属管道。主进程往管道的写端发任务,子进程从管道的读端收任务。由于管道读端默认是阻塞的,这天然就实现了一个同步控制的效果——有任务就处理,没任务就等着。

    三、代码

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define POLL_SIZE 4
    // ***********************************任务列表*********************************************
    void SyncDisk() {
        std::cout << "同步磁盘..." << std::endl;
    }
    void DownloadFile() {
        std::cout << "下载文件..." << std::endl;
    }
    void PrintMessage() {
        std::cout << "打印消息..." << std::endl;
    }
    void UpdateDatabase() {
        std::cout << "更新数据库..." << std::endl;
    }
    typedef void (*task_t)();   // 定义一个函数指针类型,指向任务函数
    task_t tasks[] = {SyncDisk, DownloadFile, PrintMessage, UpdateDatabase};  // 任务列表
    // ***********************************进程池实现*******************************************
    enum {
        OK = 0,
        PIPE_ERROR,
        FORK_ERROR
    };
    void Dotask(int fd) {   // 任务的入口
        while (1) {
            int task_code = 0;
            ssize_t n = read(fd, &task_code, sizeof(task_code));    // 约定好读取4字节的任务码
            if (n == -1) {
                std::cerr << "read error" << std::endl;
                break;
            }
            else if (n == 0) {
                std::cout << "没有任务了..." << std::endl;
                break; // 管道被关闭,退出循环
            }
            else {
                if (task_code < 0 || task_code >= sizeof(tasks) / sizeof(task_t)) {
                    std::cerr << "Invalid task code: " << task_code << std::endl;
                    continue; // 跳过无效的任务码
                }
                // 根据任务码执行对应的任务函数
                tasks[task_code]();
            }
        }
    }
    // typedef std::function cb_t;
    using cb_t = std::function;
    class ProcessPool {
    private:
        // 定义一个内部Channel类,保存管道写端和子进程pid
        class Channel {
        public:
            Channel(int wfd, pid_t sub_pid)
                : _wfd(wfd), _sub_pid(sub_pid)
            {
                _sub_name = "sub_channel_" + std::to_string(sub_pid);
            }
            ~Channel()
            {
            }
            void Write(int task_index) {
                ssize_t n = write(_wfd, &task_index, sizeof(task_index));
                (void)n; // 忽略写入结果
            }
            void PrintInfo() {
                printf("Channel Info - Sub PID: %d, Sub Name: %sn", _sub_pid, _sub_name.c_str());
            }
            std::string GetSubName() const {
                return _sub_name;
            }
            void ClosePipe() {
                std::cout << "关闭管道wfd: " << _wfd << std::endl;
                close(_wfd);
            }
            void Wait() {
                waitpid(_sub_pid, nullptr, 0);
                std::cout << "回收子进程: " << " PID: " << _sub_pid << std::endl;
            }
        private:
            int _wfd;              // 写管道文件描述符
            pid_t _sub_pid;        // 子进程pid
            std::string _sub_name; // 子进程名字
        };
    public:
        ProcessPool() {
            srand((unsigned int)time(nullptr) ^ (unsigned int)getpid()); // 设置随机数种子
        }
        ~ProcessPool(){}
        void Init(cb_t cb) {
            CreatProcessChannel(cb);
        }
        void Debug() {
            for (auto& ch : channels) {
                ch.PrintInfo();
            }
        }
        void Run() {
            int cnt = 6;
            while (cnt--) {
                std::cout << "---------------------------------------------" << std::endl;
                // 1. 选择一个任务
                int itask = SelectTask();
                std::cout << "选择任务: index: " << itask << std::endl;
                // 2. 选择一个channel(管道+子进程),本质是选择一个下标
                int index = SelectChannel();
                std::cout << "选择管道: index: " << index << std::endl;
                // 3. 发送任务给指定的channel(管道+子进程)
                SendTaskToChannel(itask, index);
                std::cout << "发送任务 index " << itask << " 到管道 index " << index << std::endl;
            }
        }
        void Quit() {
            // 想要1 : 1地回收,就要改变CreateProcessChannel的实现
            // 就要在子进程中关闭历史遗留的管道写端
            // 从而实现1 : 1地回收资源。
            for (auto& ch : channels) {
                ch.ClosePipe(); // 关闭写端
                ch.Wait();
            }
            // 逆向回收资源
            // int end = channels.size() - 1;
            // for (int i = end; i >= 0; --i) {
            //     channels[i].ClosePipe(); // 关闭写端
            //     channels[i].Wait();      // 等待子进程退出
            // }
            // 1:1回收演示
            // for (auto& ch : channels) {
            //     ch.ClosePipe(); // 关闭写端
            //     ch.Wait();
            // }
            // // 1. 关闭所有管道写端,通知子进程退出
            // for (auto& ch : channels) {
            //     ch.ClosePipe(); // 关闭写端
            // }
            // // 2. 等待所有子进程退出
            // for (auto& ch : channels) {
            //     ch.Wait();
            // }
        }
    private:
        int SelectChannel() {
            // 这里简单的轮询选择一个channel
            static int index = 0;
            int selected = index % channels.size();
            ++index;
            return selected;
        }
        int SelectTask() {
            // 轮询选择一个任务
            int itask = rand() % (sizeof(tasks) / sizeof(task_t));
            return itask;
        }
        void SendTaskToChannel(int itask, int index) {
            if (itask < 0 || itask >= sizeof(tasks) / sizeof(task_t)) {
                std::cerr << "Invalid task index: " << itask << std::endl;
                return;
            }
            if (index < 0 || index >= channels.size()) {
                std::cerr << "Invalid channel index: " << index << std::endl;
                return;
            }
            // 将任务索引写入管道,通知子进程执行对应的任务
            channels[index].Write(itask);
        }
        void CreatProcessChannel(cb_t cb) {
            for (int i = 0; i < POLL_SIZE; ++i) {
                int pipefd[2] = {0};
                int ret = pipe(pipefd);
                if (ret == -1) {
                    std::cerr << "pipe error" << std::endl;
                    exit(PIPE_ERROR);
                }
                pid_t pid = fork();
                if (pid == -1) {
                    std::cerr << "fork error" << std::endl;
                    exit(FORK_ERROR);
                }
                else if (pid == 0) {
                    // 子进程
                    if (!channels.empty()) {
                        for (auto& ch : channels) {
                            ch.ClosePipe(); // 关闭历史遗留的管道写端
                        }
                    }
                    close(pipefd[1]);   // 关闭写端
                    cb(pipefd[0]);
                    exit(OK);
                }
                else {
                    // 父进程
                    close(pipefd[0]);   // 关闭读端
                    // 创建一个channel对象,保存管道写端和子进程pid
                    channels.emplace_back(pipefd[1], pid);
                    // Channel ch(pipefd[1], pid);
                    // channels.emplace_back(ch);  // 将channel对象添加到容器中
                    std::cout << "创建了一个管道PID: " << pid << "到管道容器中了" << std::endl;
                    sleep(1);
                }
            }
        }
    private:
        std::vector channels;    // 要有未来组织所有channel的容器
    };
    int main() {
        // 1. 初始化一个进程池对象
        ProcessPool pool;
        pool.Init(Dotask);
        // 2. 运行进程池
        pool.Run();
        // 3. 结束进程池释放资源
        pool.Quit();
        return 0;
    }

    Linux 进程池(ProcessPool)源码解析

    这部分我们从源码角度,深入拆解一下这个进程池的实现。目标很清楚:一个父进程调度任务,多个子进程执行任务,父子之间用匿名管道通信——父进程发任务码,子进程根据码值执行对应函数。

    一、项目目标

    实现一个简单的进程池:

    • 父进程负责调度任务
    • 子进程负责执行任务
    • 父子进程通过匿名管道通信
    • 父进程发送任务码
    • 子进程根据任务码执行对应任务

    二、整体架构

    C++实现进程池:主从架构、管道通信与任务调度(示例详解)

                         Parent(ProcessPool)
                                  │
             ┌────────────────────┼────────────────────┐
             │                    │                    │
             │                    │                    │
          pipe0                pipe1                pipe2
             │                    │                    │
             ▼                    ▼                    ▼
          Child0               Child1               Child2
             │                    │                    │
          Dotask()             Dotask()             Dotask()
             │                    │                    │
             └────────────执行任务────────────┘

    父进程即 ProcessPool 对象。它的核心工作就是前面说的:创建子进程、创建管道、选择任务、分发任务、回收子进程。子进程呢?只管执行任务。

    三、任务系统

    任务定义

    void SyncDisk();
    void DownloadFile();
    void PrintMessage();
    void UpdateDatabase();
    

    任务表

    typedef void (*task_t)();
    task_t tasks[] =
    {
        SyncDisk,
        DownloadFile,
        PrintMessage,
        UpdateDatabase
    };

    本质就是一个映射:任务码 → 函数地址。

    任务码任务
    0SyncDisk
    1DownloadFile
    2PrintMessage
    3UpdateDatabase

    四、ProcessPool类设计

    class ProcessPool
    {
    private:
        vector channels;
    };
    

    这个类内部用一个 vector 保存了所有的 Channel 对象。每个 Channel 本质上就绑定了一组关系:一个管道写端 + 一个子进程 PID。

    五、Channel设计

    Channel是什么

    可以把它理解为一个“通信单元”,包含:

    一个管道 + 一个子进程

    类结构

    class Channel
    {
    private:
        int _wfd;          // 管道写端,父进程通过它发送任务
        pid_t _sub_pid;    // 子进程PID,用于waitpid回收
        string _sub_name;  // 调试用的名字,比如 "sub_channel_1234"
    };

    六、进程创建流程

    CreateProcessChannel()

    这个函数的核心就两行代码:

    pipe(pipefd);
    fork();

    然后根据 fork 的返回值,分别处理父进程和子进程逻辑。每次循环创建一个子进程和一条管道,子进程关闭管道写端,父进程关闭管道读端。

    整个创建过程是逐步构建的:第一次循环生成 Child0 和 pipe0;第二次循环生成 Child1 和 pipe1,此时父进程、Child0、Child1 都共享了 pipe0 的文件描述符;第三次循环同理。最终会形成:

    Parent
    │
    ├── Child0
    ├── Child1
    ├── Child2
    └── Child3

    七、为什么关闭历史遗留写端

    这是一个非常关键且容易踩坑的地方。由于 fork 会继承所有打开的文件描述符,假设我们创建了 Child0,它持有 pipe0[0] 和 pipe0[1](虽然子进程关闭了 pipe0[1],但父进程的 pipe0[1] 依然开着)。当继续 fork Child1 时,Child1 同样会继承父进程的所有文件描述符,其中包括 pipe0[1]。

    这就导致了一个问题:父进程关闭了 pipe0 的写端,但 Child1 手上还捏着 pipe0 的写端,那么 Child0 的 read 就无法收到 EOF。所以,子进程在创建后,必须关闭所有之前创建的、属于其他子进程的管道写端。代码中是这样处理的:

    if (!channels.empty()) {
        for (auto& ch : channels) {
            ch.ClosePipe();
        }
    }

    最终的效果是:每个管道只对应一个子进程,形成 1:1 的专属通信关系。

    八、任务执行流程

    Dotask()

    void Dotask(int fd) {
        while(true) {
            read(fd, &task_code, sizeof(task_code));
            // 校验后执行 tasks[task_code]();
        }
    }

    子进程阻塞在 read 上,直到父进程往对应的管道写端写入了任务码。子进程读到任务码后,比如读到 2,就会去调用 tasks[2](),也就是 PrintMessage()。

    整个链路很清晰:父进程 write(2) → 管道 → 子进程 read() → tasks[2]() → PrintMessage()。

    九、任务调度策略

    这里的调度策略相对简单粗暴,但足以说明问题:

    • SelectTask():通过 rand() % 4 随机选择一个任务码(0~3)。
    • SelectChannel():采用经典的 Round Robin(轮询) 策略。static int index 从0开始,每次取模 channels 的大小,依次选中每个子进程。

    执行顺序就是:0, 1, 2, 3, 0, 1, 2, 3... 保证所有子进程均衡地获得任务。

    十、运行流程图

    ┌───────────┐
    │  Run()    │
    └─────┬─────┘
          │
          ▼
    选择任务 (SelectTask)
          │
          ▼
    选择Channel (SelectChannel)
          │
          ▼
    Write(任务码) 到对应管道
          │
          ▼
    子进程read() 阻塞返回
          │
          ▼
    执行任务函数
          │
          ▼
    继续等待下一个任务

    十一、退出流程

    优雅退出是进程池设计里不可忽视的一环。流程是这样的:

    父进程:for (auto& ch : channels) { ch.ClosePipe(); }
    子进程:read() 返回 0(EOF),break 退出循环。
    父进程:for (auto& ch : channels) { ch.Wait(); }  // waitpid 回收资源

    父进程先关闭所有管道写端,一旦写端关闭,子进程的 read 就会返回0,表示“没有任务了”,子进程据此退出循环。然后父进程调用 waitpid 回收子进程资源。完成。

    十二、源码中的亮点

    这个实现里有一个地方值得单独拎出来说——回调函数的设计。它用的是 std::function 作为回调类型:

    using cb_t = std::function;
    pool.Init(Dotask);
    // 在CreateProcessChannel中,子进程调用 cb(pipefd[0])

    这样做的好处是什么?进程池的框架和具体的业务逻辑实现了解耦。框架负责进程创建、管道管理、任务分发和资源回收;而业务逻辑,也就是子进程拿到任务后具体要做什么,完全由传入的回调函数 Dotask 决定。想换一套任务处理逻辑?换一个函数指针就行了。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    下一篇: C#栈和队列详解
    相关文章 更多
    精品专题 更多
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    IOS软件

    正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

    AI

    正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

    Mac软件 更多
    Postman macOS版
    Postman macOS版

    Postman是一套面向API全生命周期的开发与测试平台,可在Mac上发送和调试多种协议请求,管理集合、环境及认证信息,并完成脚本测试、模拟服务、监控、文档发布和团队协作。它适合开发、测试及运维人员使用,提供个人免费方案。

    Docker Desktop macOS版
    Docker Desktop macOS版

    Docker Desktop 是 Docker 官方为 Mac 提供的容器开发环境,集成 Docker Engine、命令行工具、Compose、Build、Kubernetes 与图形化管理界面。

    Android Studio macOS版
    Android Studio macOS版

    Android Studio 是 Google 面向安卓应用开发提供的官方集成开发环境,基于 IntelliJ IDEA 构建。它整合智能代码编辑、Gradle 构建、界面预览、设备模拟、性能分析、调试测试及 APK 分析等工具。

    Xcode macOS版
    Xcode macOS版

    Xcode 是苹果面向 Mac 提供的官方集成开发环境,将代码编辑、项目构建、SwiftUI 实时预览、设备模拟、自动化测试、断点调试、性能分析和应用分发整合在同一套工作流中,适合开发与维护 iPhone、iPad、Mac、Apple Wa

    JetBrains WebStorm macOS版
    JetBrains WebStorm macOS版

    JetBrains WebStorm 是面向 JavaScript、TypeScript 和现代 Web 技术的专业集成开发环境,可智能理解项目结构,提供代码补全、重构、错误检查、运行调试、测试及 Git 工具。

    PhpStorm macOS版
    PhpStorm macOS版

    PhpStorm 是 JetBrains 推出的专业 PHP 集成开发环境,可在 Mac 上完成代码编写、智能检查、重构、调试、测试和数据库管理。它支持主流 PHP 框架、Composer、Git、Docker 与远程解释器。

    JetBrains PyCharm macOS版
    JetBrains PyCharm macOS版

    JetBrains PyCharm 是面向 Python 开发者的集成开发环境,提供智能补全、代码检查、重构、调试、测试、版本控制及虚拟环境管理功能。Mac 版兼容 Intel 与 Apple 芯片,核心功能可免费使用。

    JetBrains IntelliJ IDEA macOS版
    JetBrains IntelliJ IDEA macOS版

    IntelliJ IDEA 是 JetBrains 推出的专业集成开发环境,重点服务于 Java、Kotlin 与 JVM 项目开发。软件集成代码补全、静态检查、重构、调试、测试、版本控制及构建工具,并可借助插件扩展语言和框架支持。

    Visual Studio Code macOS版
    Visual Studio Code macOS版

    Visual Studio Code 是微软提供的免费代码编辑器,支持 macOS,并为 Intel 与 Apple 芯片 Mac 提供适配版本。软件集代码编辑、调试、Git 源代码管理、集成终端和扩展市场于一体。

    REAPER macOS版
    REAPER macOS版

    REAPER是Cockos开发的数字音频工作站,提供多轨音频与MIDI录制、剪辑、处理、混音和母带制作工具。Mac版兼容Intel与Apple芯片,支持AU、VST、VST3、CLAP等插件格式,并提供高度可定制的工作流程。

    Adobe Audition macOS版
    Adobe Audition macOS版

    Adobe Audition 是面向播客、影视后期、音乐制作及内容创作者的专业音频工作站,提供波形精修、多轨录制与混音、频谱编辑、降噪修复、响度匹配和效果处理工具,并可与 Adobe Premiere 协同完成视频声音制作。

    Pro Tools macOS版
    Pro Tools macOS版

    Pro Tools 是 Avid 推出的专业数字音频工作站,覆盖多轨录音、无损剪辑、MIDI 编曲、插件处理、自动化混音及声音与画面同步等工作。它拥有 Intro、Artist、Studio 和 Ultimate 等方案。

    WINDOWS 更多
    PixPix 网页版
    PixPix 网页版

    PixPix 是面向品牌、卖家和内容团队的在线 AI 图片视频创作平台,提供图视频 Agent、无限画布、图片生成、视频生成、自然语言编辑及商品套图、产品精修和带货视频等电商工具。

    Postman Windows版
    Postman Windows版

    Postman 是面向 API 全生命周期的开发与协作平台。Windows 桌面版可用于构建和发送 HTTP、GraphQL、gRPC、WebSocket、MQTT 等请求,并提供环境变量、集合、脚本测试、模拟服务、监控、文档及团队工作区等

    Docker Desktop Windows版
    Docker Desktop Windows版

    Docker Desktop 为 Windows 开发者提供集成式容器环境,内置 Docker Engine、命令行工具、Compose、Build 与 Kubernetes 等组件,可在本机完成镜像构建、容器运行、日志查看和资源管理。

    Android Studio Windows版
    Android Studio Windows版

    Android Studio 是 Google 面向 Android 应用开发提供的官方集成开发环境,以 IntelliJ IDEA 为基础,集成智能代码编辑、Gradle 构建、设备模拟、界面预览、调试测试及性能分析工具,

    JetBrains WebStorm Windows版
    JetBrains WebStorm Windows版

    WebStorm 是 JetBrains 面向 JavaScript、TypeScript 和现代 Web 技术打造的集成开发环境。它提供项目级代码分析、智能补全、安全重构、导航搜索,并集成调试、测试、终端、Git 与常用前端工具,

    JetBrains PhpStorm Windows版
    JetBrains PhpStorm Windows版

    PhpStorm 是 JetBrains 面向 PHP 与现代 Web 开发打造的专业集成开发环境,提供代码补全、静态检查、重构、调试、测试、数据库、版本控制及前端语言支持。Windows 用户可直接下载安装程序,

    JetBrains PyCharm Windows版
    JetBrains PyCharm Windows版

    PyCharm 是 JetBrains 推出的 Python 集成开发环境,提供智能代码补全、错误检查、重构、调试、测试、Git、终端及 Jupyter Notebook 支持。统一版安装后可体验 30 天 Pro 功能,

    JetBrains IntelliJ IDEA Windows版
    JetBrains IntelliJ IDEA Windows版

    JetBrains IntelliJ IDEA 是以 Java、Kotlin 和 JVM 项目为核心的专业集成开发环境,提供智能补全、代码检查、安全重构、调试、测试、版本控制及构建工具集成。统一发行版的核心功能可免费用于商业和非商业项目,

    Visual Studio Code Windows版
    Visual Studio Code Windows版

    Visual Studio Code 是微软推出的免费代码编辑器,Windows 版集成智能补全、代码导航、调试、终端和版本控制,并可通过扩展添加语言、格式化及远程开发能力。它启动较快、配置灵活,

    REAPER Windows版
    REAPER Windows版

    REAPER 是 Cockos 推出的数字音频工作站,适用于录音、MIDI 编曲、音频编辑、混音、母带制作及声音设计。软件安装包小巧,支持多轨录制、灵活路由、自动化、第三方插件和脚本扩展,并提供完整功能的 60 天免费评估期。

    Adobe Audition Windows版
    Adobe Audition Windows版

    Adobe Audition 是面向播客、影视后期、广播与音乐制作的专业音频工作站,提供波形编辑、多轨混音、频谱分析、录音、降噪修复和效果处理等功能。Windows 版通过 Adobe Creative Cloud 安装和管理,

    Pro Tools Windows版
    Pro Tools Windows版

    Pro Tools 是 Avid 推出的专业数字音频工作站,提供多轨录音、音频剪辑、MIDI编曲、插件处理、自动化混音及影视声音制作能力。它适合音乐人、录音师、制作人和音频后期团队使用,并提供免费入门版、订阅方案及永久授权选择。