【Linxu】:进程池

朋友们、伙计们,我们又见面了,本期来给大家带来进程池的相关代码和知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

C + + 专 栏   :C++

Linux 专 栏  :Linux

 

目录

1. 进程池

2. 进程池的简单实现

2.1 创建管道和进程

2.1.1 管理信道

2.2  完成任务

2.2.1 选择任务

2.2.2 选择信道

2.2.3 发送任务 

2.2.4 接受任务

2.2.5 封装 

2.3 回收资源

2.4 处理创建进程和管道时细节问题 

3. 完整代码


1. 进程池

创建进程我们调用的是fork系统调用,系统调用也是有成本的,当我们有很多需要执行的任务时,与其再分配任务的时候一个一个创建,倒不如先创建一批进程,然后将任务进行分配,这样做可以大大提高效率,这种方式就叫做进程池。

2. 进程池的简单实现

我们使用匿名管道的方式由主进程向各个子进程分配任务。  

所以通信方式是父进程写入,子进程读取。

2.1 创建管道和进程

每个子进程和父进程传输任务都需要有对应的一个“信道”,所以我们使用循环创建进程并创建管道,为了正常分配任务,所以需要关闭不需要的文件描述符,形成单向信道(父进程写入,子进程读取)。并且我们将管道进行重定向,直接从标准输入里面读取即可。

#include <iostream>
#include <unistd.h>
#include <cassert>

#define NUM 5  // 创建进程以及管道的个数


int main()
{
    for(int i = 0; i < NUM; i++)
    {
        // 创建管道
        int pipefd[2];
        int n = pipe(pipefd);
        assert(n == 0);
        (void)n;  // 防止告警
        // 创建进程
        pid_t id = fork();
        assert(id != -1);
        if(id == 0)
        {
            // child
            // 构建单向信道
            close(pipefd[1]);
            dup2(pipefd[0], 0); // 将标准输入指定为从pipefd[0]中读取

            // ......
            exit(0);
        }
        // parent
        close(pipefd[0]);
    }
    return 0;
}

2.1.1 管理信道

我们创建的多个管道、进程,当主进程通过管道给子进程分配任务的时候,如何知道通过哪个管道分配给了哪个进程,所以也需要对信道进行管理(先描述再组织),所以创建一个channel的类,类成员设置:写给哪个管道、哪个进程读取、信道名称。

// 定义信道
class channel
{
public:
    channel(int fd, pid_t id)
        : ctrlfd(fd), workid(id)
    {
        name = "channel-" + std::to_string(number++);
    }
public:
    int ctrlfd;
    pid_t workid;
    std::string name;
};

定义好信道,所以还需要将其管理起来,通过vector将这些信道管理,将信道的管理工作变成了对数组的增删查改,创建一个信道,我们就添加一个信道。

顺带我们将创建信道以及创建管道的过程封装起来:

// 创建信道、创建进程
void CreateChannels(std::vector<channel> *c)
{
    for (int i = 0; i < NUM; i++)
    {
        // 创建管道
        int pipefd[2];
        int n = pipe(pipefd);
        assert(n == 0);
        (void)n; // 防止告警
        // 创建进程
        pid_t id = fork();
        assert(id != -1);
        if (id == 0)
        {
            // child
            // 构建单向信道
            close(pipefd[1]);
            // ......

            exit(0);
        }
        // parent
        close(pipefd[0]);
        // 添加信道
        c->push_back(channel(pipefd[1], id));
    }
}

2.2  完成任务

完成任务首先都有任务列表,所以我们就用简单的打印函数来演示一下,创建一个Task.hpp文件作为任务列表:

#pragma once

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <functional>
#include <vector>

typedef std::function<void()> task_t;

void DownLoad()
{
    std::cout << "正在进行下载" << std::endl
              << " 处理者: " << getpid() << std::endl;
}

void PrintLog()
{
    std::cout << "正在进行打印日志" << std::endl
              << " 处理者: " << getpid() << std::endl;
}

void PushVideoStream()
{
    std::cout << "正在进行推送视频流" << std::endl
              << " 处理者: " << getpid() << std::endl;
}

class Init
{
public:
    // 任务码
    const static int g_down_load_code = 0;
    const static int g_print_log_code = 1;
    const static int g_push_videostream_code = 2;
    // 任务集合
    std::vector<task_t> tasks;

public:
    Init()
    {
        // 添加任务至任务列表
        tasks.push_back(DownLoad);
        tasks.push_back(PrintLog);
        tasks.push_back(PushVideoStream);
    }
    // 检查任务的合法性
    bool CheckSafe(int code)
    {
        if (code <= 0 && code < tasks.size())
            return true;
        else
            return false;
    }
};
// 定义对象
Init init;

2.2.1 选择任务

选择任务可以在任务列表中通过任务码随机选择任务;

直接在任务列表里面添加选择任务的成员函数:

class Init
{
public:
    // 任务码
    const static int g_down_load_code = 0;
    const static int g_print_log_code = 1;
    const static int g_push_videostream_code = 2;
    // 任务集合
    std::vector<task_t> tasks;

public:
    Init()
    {
        // 添加任务至任务列表
        tasks.push_back(DownLoad);
        tasks.push_back(PrintLog);
        tasks.push_back(PushVideoStream);
        // 设置随机数
        srand(time(nullptr) ^ getpid());
    }
    // 运行任务
    void RunTask(int code)
    {
        return tasks[code]();
    }
    // 随机选择任务
    int SelectTask()
    {
        return rand() % tasks.size();
    }
    std::string ToDesc(int code)
    {
        switch (code)
        {
        case g_down_load_code:
            return "Download";
        case g_print_log_code:
            return "PrintLog";
        case g_push_videostream_code:
            return "PushVideoStream";
        default:
            return "Unknow";
        }
    }
};

2.2.2 选择信道

选择信道我们采用轮询的方式选择:

int main()
{
    std::vector<channel> channels;
    // 创建进程和管道
    CreateChannels(&channels);

    // 开始完成任务
    int pos = 0;
    while(true)
    {
        // 1. 选择任务
        int command = init.SelectTask();

        // 2. 选择信道
        const auto& c = channels[pos++];
        pos %= channels.size();

        // 3. 发送任务
        
    }
    return 0;
}

2.2.3 发送任务 

使用write系统调用,直接向标准输入里面写入,将控制描述符和任务编号发送给子进程。

int main()
{
    std::vector<channel> channels;
    // 创建进程和管道
    CreateChannels(&channels);

    // 开始完成任务
    int pos = 0;
    while(true)
    {
        // 1. 选择任务
        int command = init.SelectTask();

        // 2. 选择信道
        const auto& c = channels[pos++];
        pos %= channels.size();

        // 3. 发送任务
        write(c.ctrlfd, &command, sizeof(command));
    }
    return 0;
}

2.2.4 接受任务

为了方便,我们直接将管道重定向至标准输入,在接受任务时,直接从标准输入里面接受任务,我们规定标准一次按照四字节读取,在接受到任务之后先得判断任务的合法性,再去执行任务。

void Work()
{
    while (true)
    {
        // 读取任务
        int code = 0;
        ssize_t n = read(0, &code, sizeof(code));
        if (n == sizeof(code))
        {
            // 读成功
            if (!init.CheckSafe(code)) // 任务合法性判断
                continue;
            init.RunTask(code);
        }
        else if (n == 0)
        {
            // 失败
            break;
        }
        else
        {
            // Do Nothing
        }
    }
}

2.2.5 封装 

将选择任务、选择信道、发送任务封装在一起,并且想实现一个给定次数执行多少次任务,如果没有指定,就一直发送:

void SendCommand(const std::vector<channel> &channels, bool flag, int num = -1)
{
    int pos = 0;

    while (true)
    {
        // 1. 选择任务
        int command = init.SelectTask();

        // 2. 选择信道
        auto &channel = channels[pos++];
        pos %= channels.size();

        // debug
        std::cout << "send command " << init.ToDesc(command) << "[" << command << "]"
                  << " in "
                  << channel.name << " worker is : " << channel.workid << std::endl;

        // 3. 发送任务
        write(channel.ctrlfd, &command, sizeof(command));

        // 4. 判断是否要退出
        if (!flag)
        {
            num--;
            if (num <= 0)
                break;
        }
        sleep(1);
    }
    std::cout << "SendCommand done..." << std::endl;
}

2.3 回收资源

在子进程退出之后父进程首先需要对子进程进行等待,还需要回收信道,根据管道的读写情况,当管道的写端关闭,OS就会杀掉写端进程,所以回收资源我们先等待子进程,再关闭写端:

void ReleaseChannel(const std::vector<channel> &c)
{
    for (auto &channels : c)
    {
        close(channels.ctrlfd);
        pid_t rid = waitpid(channels.workid, nullptr, 0);
        if (rid == channels.workid)
        {
            std::cout << "wait child: " << channels.workid << " success" << std::endl;
        }
    }
}

2.4 处理创建进程和管道时细节问题 

第一次父进程创建子进程的时候,子进程会继承父进程的文件描述符表,但是在第二次创建子进程的时候,该子进程也会将父进程的文件描述符表继承下去,此时的文件描述符表还有指向第一次的管道,继承了父进程的写端,这就导致了在回收资源的时候会出错。

在回收时可以采用先全部关闭所有的管道,再等待子进程;

还可以采用倒着回收资源的方式。

还可以修改创建进程的方式:

定义临时的容器, 关闭每个子进程不需要的写端。

void Printfd(const std::vector<int> &fds)
{
    std::cout << getpid() << " close fds: ";
    for (auto fd : fds)
    {
        std::cout << fd << " ";
    }
    std::cout << std::endl;
}

void CreateChannels(std::vector<channel> *c)
{
    std::vector<int> temp;
    for (int i = 0; i < num; i++)
    {
        // 1.定义并创建管道
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        assert(n == 0);
        (void)n;

        // 2.创建进程
        pid_t id = fork();
        assert(id != -1);

        // 3.构建单向通信信道
        if (id == 0) // child
        {
            if (!temp.empty())
            {
                for (auto fd : temp)
                {
                    close(fd);
                }
                Printfd(temp);  // 打印子进程关闭不需要的写端
            }
            close(pipefd[1]);
            dup2(pipefd[0], 0); // 将标准输入指定为从pipefd[0]中读取
            Work();
            exit(0);
        }

        // father
        close(pipefd[0]);
        c->push_back(channel(pipefd[1], id));
        temp.push_back(pipefd[1]);  // 将父进程打开的写端记录下来
    }
}

3. 完整代码

Task.hpp:

#pragma once

#include <iostream>
#include <functional>
#include <vector>
#include <ctime>
#include <unistd.h>

typedef std::function<void()> task_t;

void DownLoad()
{
    std::cout << "正在进行下载" << std::endl
              << " 处理者: " << getpid() << std::endl;
}

void PrintLog()
{
    std::cout << "正在进行打印日志" << std::endl
              << " 处理者: " << getpid() << std::endl;
}

void PushVideoStream()
{
    std::cout << "正在进行推送视频流" << std::endl
              << " 处理者: " << getpid() << std::endl;
}

class Init
{
public:
    // 任务码
    const static int g_down_load_code = 0;
    const static int g_print_log_code = 1;
    const static int g_push_videostream_code = 2;
    // 任务集合
    std::vector<task_t> tasks;

public:
    Init()
    {
        tasks.push_back(DownLoad);
        tasks.push_back(PrintLog);
        tasks.push_back(PushVideoStream);

        srand(time(nullptr) ^ getpid());
    }

    bool CheckSafe(int code)
    {
        if (code <= 0 && code < tasks.size())
            return true;
        else
            return false;
    }
    void RunTask(int code)
    {
        return tasks[code]();
    }

    int SelectTask()
    {
        return rand() % tasks.size();
    }

    std::string ToDesc(int code)
    {
        switch (code)
        {
        case g_down_load_code:
            return "Download";
        case g_print_log_code:
            return "PrintLog";
        case g_push_videostream_code:
            return "PushVideoStream";
        default:
            return "Unknow";
        }
    }
};
// 定义对象
Init init;

源ProcessPool.cc:

#include <iostream>
#include <unistd.h>
#include <cassert>
#include <vector>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>

#include "Task.hpp"

const static int num = 5;
static int number = 1;

// 信道的定义
class channel
{
public:
    channel(int fd, pid_t id)
        : ctrlfd(fd), workid(id)
    {
        name = "channel-" + std::to_string(number++);
    }

public:
    int ctrlfd;
    pid_t workid;
    std::string name;
};

void Work()
{
    while (true)
    {
        int code = 0;
        ssize_t n = read(0, &code, sizeof(code));
        if (n == sizeof(code))
        {
            // 读成功
            if (!init.CheckSafe(code))
                continue;
            init.RunTask(code);
        }
        else if (n == 0)
        {
            // 失败
            break;
        }
        else
        {
            // Do Nothing
        }
    }
}

void Printfd(const std::vector<int> &fds)
{
    std::cout << getpid() << " close fds: ";
    for (auto fd : fds)
    {
        std::cout << fd << " ";
    }
    std::cout << std::endl;
}

// 创建管道和进程
void CreateChannels(std::vector<channel> *c)
{
    std::vector<int> temp;
    for (int i = 0; i < num; i++)
    {
        // 1.定义并创建管道
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        assert(n == 0);
        (void)n;

        // 2.创建进程
        pid_t id = fork();
        assert(id != -1);

        // 3.构建单向通信信道
        if (id == 0) // child
        {
            if (!temp.empty())
            {
                for (auto fd : temp)
                {
                    close(fd);
                }
                Printfd(temp);  // 打印子进程关闭不需要的写端
            }
            close(pipefd[1]);
            dup2(pipefd[0], 0); // 将标准输入指定为从pipefd[0]中读取
            Work();
            exit(0);
        }

        // father
        close(pipefd[0]);
        c->push_back(channel(pipefd[1], id));
        temp.push_back(pipefd[1]);  // 将父进程打开的写端记录下来
    }
}

// 发送任务
void SendCommand(const std::vector<channel> &channels, bool flag, int num = -1)
{
    int pos = 0;

    while (true)
    {
        // 1. 选择任务
        int command = init.SelectTask();

        // 2. 选择信道
        auto &channel = channels[pos++];
        pos %= channels.size();

        // debug
        std::cout << "send command " << init.ToDesc(command) << "[" << command << "]"
                  << " in "
                  << channel.name << " worker is : " << channel.workid << std::endl;

        // 3. 发送任务
        write(channel.ctrlfd, &command, sizeof(command));

        // 4. 判断是否要退出
        if (!flag)
        {
            num--;
            if (num <= 0)
                break;
        }
        sleep(1);
    }
    std::cout << "SendCommand done..." << std::endl;
}

// 回收资源
void ReleaseChannel(const std::vector<channel> &c)
{
    for (auto &channels : c)
    {
        close(channels.ctrlfd);
        pid_t rid = waitpid(channels.workid, nullptr, 0);
        if (rid == channels.workid)
        {
            std::cout << "wait child: " << channels.workid << " success" << std::endl;
        }
    }
}

int main()
{
    std::vector<channel> channels;

    // 1. 创建信道,创建进程
    CreateChannels(&channels);

    // 2. 开始发送任务
    const bool g_always_loop = true;
    // SendCommand(channels, g_always_loop);   // 一直执行
    SendCommand(channels, !g_always_loop, 10); // 执行10次

    // 3. 回收资源,想让子进程退出,并且释放管道,只要关闭写端
    ReleaseChannel(channels);
    return 0;
}

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!  

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,以下是一个简单的 Linux C 语言进程筛素数的实现方式: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #define N 1000 // 筛选 1~N 的素数 // 筛素数的函数 void sieve(int n, int *primes) { int i, j; int is_prime; primes[0] = primes[1] = 0; for (i = 2; i <= n; i++) { is_prime = 1; for (j = 2; j < i; j++) { if (i % j == 0) { is_prime = 0; break; } } primes[i] = is_prime; } } // 进程中的子进程,根据起始和结束下标筛素数 void worker(int start, int end, int *primes) { int i, j; int is_prime; for (i = start; i <= end; i++) { is_prime = 1; for (j = 2; j < i; j++) { if (i % j == 0) { is_prime = 0; break; } } primes[i] = is_prime; } } int main() { int num_processes = 4; // 进程数 int primes[N + 1]; // 存储素数信息的数组 int i, j; pid_t pid; int start, end; // 初始化 primes 数组 for (i = 0; i <= N; i++) { primes[i] = 1; } // 创建进程 for (i = 0; i < num_processes; i++) { start = i * (N / num_processes) + 1; end = (i + 1) * (N / num_processes); if (i == num_processes - 1) { end = N; } pid = fork(); if (pid == 0) { worker(start, end, primes); exit(0); } } // 等待所有子进程执行完毕 for (i = 0; i < num_processes; i++) { wait(NULL); } // 输出素数 for (i = 2; i <= N; i++) { if (primes[i] == 1) { printf("%d ", i); } } printf("\n"); return 0; } ``` 该程序首先创建一个长度为 `N+1` 的整型数组 `primes`,用于存储 1~N 的素数信息。然后创建一个进程,每个子进程负责筛选一部分素数。具体来说,每个子进程根据自己的起始和结束下标,从中筛选素数,将素数信息存储在 `primes` 数组中。最后,主进程遍历 `primes` 数组,输出素数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

stackY、

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

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

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

打赏作者

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

抵扣说明:

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

余额充值