【Linux】进程间管道通信、线程池

news2025/1/11 14:51:57

目录

一、进程间通信的概念

二、匿名管道

2.1 什么是管道

2.2 管道的实现

2.3 管道的使用

三、进程池

3.1 进程池实现逻辑

3.2 模拟任务表

3.3 进程池的创建

四、命名管道

4.1 创建命名管道

4.2 命令管道的使用

一、进程间通信的概念

进程具有独立性,进程间想通信,难度比较大,所以我们使用特殊的方式让进程看到同一份结构,然后让其在公共资源中进行通信,

进程间通信的本质:先让不同的进程看到同一份资源(内存空间)

以下是进程间进行通信所能达到的目的:

数据传输:一个进程需要将它的数据发送给另一个进程

资源共享:多个进程之间共享同样的资源。

通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)

进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

进程间通信的必要性:

单进程,那么也就无法使用并发能力,更加无法实现多进程协同(传输数据,同步执行流,消息通知等)。

进程间通信的技术背景:

  • 进程是具有独立性的,虚拟地址空间+页表,保证进程运行的独立性(进程内核数据结构+进程的代码和数据)
  • 通信成本比较高

进程间通信的本质理解:

  • 进程间通信的前提,首先需要让不同的进程看到同一块"内存"(特定的结构)
  • 所以所谓的进程看到同一块内存,属于哪一个进程?不能隶属于任何一个进程,而应该强调共享。

所以本篇博客的内容主要介绍以下这几种进程间通信的方式:

1.匿名管道;2.命名管道;3.共享内存

二、匿名管道

2.1 什么是管道

  • 管道是Unix中最古老的进程间通信的形式。
  • 我们把从一个进程链接到另一个进程的一个数据流称为一个"管道"。
  • 有一个入口,有一个出口,进行单向传输内容,管道中传输的都是"资源"。

管道通信背后是进程之间通过管道进行通信。

我们实现管道通信的原理就是让两个进程分别以读写方式打开一个公共文件,而其中这个公共文件,就叫做管道,管道的本质就是文件。

2.2 管道的实现

现在我使用画图的方式演示两个进程间是如何实现管道的,首先父进程分别以读、写的形式打开一个文件:

然后我们创建子进程,子进程会拷贝父进程的files_stuct ,所以两个进程的files_stuct 就指向了相同的文件,如图所示:

然后我们关闭一个进程的读端,再关闭另一个进程的写端,实现一个进程写数据,一个进程读取数据,就实现了管道。关闭的这个动作,就是确定管道的通信方向。

即关闭不需要的文件描述符,我们来实现父进程进行写入,子进程进行读取:

2.3 管道的使用

有了上面的原理图,接下来我们就来创建管道并使用管道。

首先我们要掌握一个系统调用 pipe ,用于创建匿名管道。

函数参数:

参数中 pipefd[2] 是一个输出型参数,会返回读方式打开、写方式打开的文件描述符。

返回值:

成功返回0,错误返回-1,并设置错误码。

接下来我们就简单的使用一个这个接口,然后看看其打开的文件 fd 是多少。

#include <iostream>
#include <assert.h>
#include <unistd.h>
using namespace std;

int main()
{
    int pipefd[2] = {0};  //fd[0]是读端,fd[1]是写端
    int n = pipe(pipefd);
    assert(n != -1);
    (void)n;       // assert在release中无效,一个变量定义但没被使用会进行报错。
    cout << "pipefd[0]:" << pipefd[0] << endl;
    cout << "pipefd[1]:" << pipefd[1] << endl;
    return 0;
}

 

因为stdin、stdout、stderr对应0.1.2,所以再打开的fd就是3和4了。

好的,以上就是测试代码部分,然后我们就可以使用条件编译手段将其进行屏蔽。

这个条件编译的意思是,如果是DEBUG调试,就执行以下代码,如果不是DEBUG调试,则不执行。

接下来是我们就要创建子进程,并构建单向通信的管道。

原理是:

使用fork创建子进程,让子进程关闭写端,让子进程使用 read 函数进行读取;父进程使用 write 往对应写端的fd写入数据,然后使用 waitpid 等待子进程的退出。

int main()
{
    // 1.创建管道
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    assert(n != -1);
    (void)n;
    // 2.创建子进程
    pid_t id = fork();
    assert(id != -1);
    // 子进程 --- 读取
    if (id == 0)
    {
        // 3.创建单向通信的管道 关闭进程不需要的fd
        close(pipefd[1]); // 进行读取
        char buffer[1024];
        while (1)
        {
            ssize_t sz = read(pipefd[0], buffer, sizeof(buffer) - 1);
            if (sz > 0)
            {
                buffer[sz] = '\0';
                printf("child:[%d] receive: %s\n", getpid(), buffer);
            }
        }
        close(pipefd[0]);
        exit(0);
    }
    // 父进程 --- 写入
    close(pipefd[0]);
    string message = "i am father process ,accept the message";
    int count = 0; // 发送消息的条数
    char send_buffer[1024];
    while (1)
    {
        // snprintf--向字符串中打印内容
        snprintf(send_buffer, sizeof(send_buffer), "%s pid:[%d],count:%d", message.c_str(), getpid(), count++);
        // 将管道中写入数据
        write(pipefd[1], send_buffer, strlen(send_buffer));
        sleep(1);
    }
    // 等待子进程退出
    pid_t ret = waitpid(id, nullptr, 0);
    assert(ret < 0);
    (void)ret;
    close(pipefd[1]);
    exit(0);
    return 0;
}

运行结果如下:

其中父进程每一秒进行一次写入,子进程就将数据进行了读取。

管道的现象:

  1. 写入很快,读取很慢,写满则不能再写入了。
  2. 写入很慢,读取很快,管道没有数据时,读端等待。
  3. 写端关闭,读端返回0,标识读到了文件结尾。
  4. 读端关闭,写端继续写入,OS会终止进程。

管道特点如下:

  1. 管道是用来进行具有血缘关系的进程进行进程间通信的 --- 常用于父子通信
  2. 管道具有通过让进程间系统,提供了访问控制,即有数据再读,无数据则不读。
  3. 管道提供的是面向流式的通信服务 --- 面向字节流 -- 通过协议实现
  4. 管道是基于文件的,如果写入的一方关闭,则读取的一方read会返回0,表示读到了文件的结尾!
  5. 管道是单向通信的,即半双工通信的一种特殊情况。

三、进程池

3.1 进程池实现逻辑

上面是使用管道实现进程间通信,那管道在实际运行中我们可以做什么呢?

接下来就简单实现一个进程池,原理如下:

我们首先创建一个父进程,当用户需要执行特定任务时,我们让父进程随机调用其下的子进程去完成任务。

3.2 模拟任务表

我们先编写模拟任务、任务操作表、任务描述表。

#pragma once
#include <iostream>
#include <assert.h>
#include <string>
#include <unordered_map>
#include <vector>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <functional>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;

typedef function<void()> func;
// using func = std::function<void()>;  C++11支持的写法

vector<func> taskArr;            // 任务操作表
unordered_map<int, string> desc; // 任务描述表

// 4个模拟任务
void readMySQL()
{
    cout << "process[" << getpid() << "] 执行 # 数据库读取 # 任务\n";
}

void execuleUrl()
{
    cout << "process[" << getpid() << "] 执行 # 字符串解析 # 任务\n";
}

void subTask()
{
    cout << "process[" << getpid() << "] 执行 # 任务提交 # 任务\n";
}

void saveData()
{
    cout << "process[" << getpid() << "] 执行 # 数据存储 # 任务\n";
}

void load()
{
    desc.insert({taskArr.size(), "readMySQL:数据库读取"});
    taskArr.push_back(readMySQL);

    desc.insert({taskArr.size(), "execuleUrl:字符串解析"});
    taskArr.push_back(execuleUrl);

    desc.insert({taskArr.size(), "subTask:任务提交"});
    taskArr.push_back(subTask);

    desc.insert({taskArr.size(), "saveData:数据存储"});
    taskArr.push_back(saveData);
}

void showTask()
{
    for (const auto &Tast : desc)
    {
        cout << Tast.first << "\t" << Tast.second << endl;
    }
}

int taskSize()
{
    return taskArr.size();
}

3.3 进程池的创建

然后我们使用fork函数让父进程创建5个子进程,用户想要调用任务时,就随机分配给任意一个子进程完成任务。

#include "Test.hpp"

#define PROCESS_NUM 5

int waitingTask(int waitFd, bool &quit)
{
    uint32_t command = 0; // uint32_t 无符号4字节整形
    ssize_t s = read(waitFd, &command, sizeof(command));
    if (s == 0)
    {
        quit = true;
        return -1;
    }
    assert(s == sizeof(uint32_t)); // 返回的必须是4字节
    return command;
}

void sendAndWakeup(pid_t id, int fd, uint32_t command)
{
    write(fd, &command, sizeof(command));
    cout << "call process:[" << id << "] execute task:" << desc[command] << "through fd:" << fd << endl;
    sleep(1);
}

int main()
{
    // 代码中有一个关于fd的处理,有一个小问题,不影响使用,但是请找出
    load();
    // 父进程下的子进程表
    vector<pair<pid_t, int>> slots;
    for (int i = 0; i < PROCESS_NUM; i++) // 012
    {
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        assert(n != -1);
        (void)n;
        // 创建子进程
        pid_t id = fork();
        assert(id != -1);
        // 子进程进行读取
        if (id == 0)
        {
            close(pipefd[1]);
            while (true)
            {
                bool quit = false;
                // 子进程开始等任务
                int command = waitingTask(pipefd[0], quit); // 无任务,则阻塞,使用commmand接受任务分配
                if (quit)
                    break;
                // 执行对应任务
                if (command >= 0 && command < taskSize())
                {
                    taskArr[command]();
                }
            }
            exit(1);
        }
        // father  进行写入,关闭读端
        close(pipefd[0]);
        // 将子进程的pid 和父进程的写端fd 放入到进程表中
        slots.push_back({id, pipefd[1]});
    }

    // 现在父进程已经拥有PROCESS_NUM个子进程了 ,父进程派发任务
    srand((unsigned int)time(nullptr) ^ getpid() ^ 123123123);
    while (true)
    {
        int command;
        int select;
        // command = rand() % taskSize();
        // int choice = rand() % slots.size();
        // sendAndWakeup(slots[choice].first, slots[choice].second, command);
        cout << "***********************************************" << endl;
        cout << "***** 1.show functions     2.send command *****" << endl;
        cout << "***********************************************" << endl;
        cout << "Please select>" << endl;
        cin >> select;
        if (select == 1)
        {
            showTask();
        }
        else if (select == 2)
        {
            showTask();
            cout << "Enter Your Command>";
            // 选择任务
            cin >> command;
            // 选择进程
            int choice = rand() % taskSize();
            // 分配任务
            sendAndWakeup(slots[choice].first, slots[choice].second, command);
        }
    }

    // 关闭写端fd,所有的子进程都会退出
    for (const auto slot : slots)
    {
        close(slot.second);
    }
    // 回收子进程
    for (const auto &slot : slots)
    {
        waitpid(slot.first, nullptr, 0);
    }
    return 0;
}

输入1则打印任务表,输入2可以选择任务,然后父进程随机将任务派发给子进程。运行结果如下:

 同时我们还可以使用ps ajx | head -1 && ps axj | grep ProcessPool这个脚本来看看进程池中的进程数量,效果检测:

四、命名管道

 匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。

如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来实现,这种管道泵称为命名管道。

命名管道是一种特殊的文件类型.

4.1 创建命名管道

命名管道可以从命令行上创建,命令行方法是使用下面这个命令。

使用:

 发送与接收

 接下来就是在我们的程序中创建管道

返回值:

        成功返回0,失败返回-1;

参数:

        第一个参数是传入待创建管道的管道名,第二个参数是待创建管道的权限(#define MODE 0666)

4.2 命令管道的使用

此时我们可以模拟服务端与客户端的交互,服务端创建管道文件并打开读取,客户端打开服务端创建的管道文件进行写入数据。

然后我们将服务端进行优化,创建3个子进程,3个子进程都处于同时等待读数据的模式,3个子进程争取读数据。

服务端(server)

#include "comm.hpp"
#include "log.hpp"

static void getMessage(int fd)
{
    char buffer[BUFFSIZE];
    while (1)
    {
        memset(buffer, '\0', sizeof(buffer));
        ssize_t s = read(fd, buffer, sizeof(buffer));
        if (s > 0)
        {
            cout << "[" << getpid() << "]"
                 << "cliet messages>" << buffer << endl;
        }
        else if (s == 0)
        {
            // end of file
            cerr << "[" << getpid() << "]"
                 << "client quit,read end" << endl;
            break;
        }
        else
        {
            // read error
            perror("read");
            break;
        }
    }
}

int main()
{
    // 1.创建管道文件
    if (mkfifo(ipcPath.c_str(), MODE) < 0)
    {
        perror("mkfifo");
        exit(1);
    }
    Log("创建管道文件成功", Debug) << "step 1" << endl;
    //  2.正常的文件操作
    int fd = open(ipcPath.c_str(), O_RDONLY);
    if (fd < 0)
    {
        perror("open");
        exit(2);
    }
    Log("打开管道文件成功", Debug) << "step 2" << endl;

    int nums = 3;
    for (int i = 0; i < nums; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {
            // 编写正常的通信代码
            getMessage(fd);
            exit(1);
        }
    }

    for (int i = 0; i < nums; i++)
    {
        waitpid(-1, nullptr, 0);
    }

    // 关闭文件
    close(fd);
    Log("关闭管道文件成功", Debug) << "step 3" << endl;
    // 删除文件
    unlink(ipcPath.c_str());
    Log("删除管道文件成功", Debug) << "step 4" << endl;
    return 0;
}

客户端(client)

#include "comm.hpp"
#include "log.hpp"

int main()
{
    // 1.指向同一个空间
    int fd = open(ipcPath.c_str(), O_WRONLY);
    if (fd < 0)
    {
        perror("open");
        exit(1);
    }
    // ipc通信
    string buffer;
    while (1)
    {
        cout << "Please enter the messages" << endl;
        getline(cin, buffer);
        write(fd, buffer.c_str(), buffer.size());
    }
    close(fd);

    return 0;
}

让子进程抢数据。grep axj | grep mysever

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/163370.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

面试系列:单点登录的知识(一)

大家好&#xff0c;我是车辙&#xff0c;由于目前接手的业务涉及到了单点登录&#xff0c;所以一直在疯狂的去补充这方面的知识。也写下了这篇面试形式的文章&#xff0c;写的不好大家轻点 Diss。 面试开始 在焦急的等待中&#xff0c;一位看上去比较年轻的小伙子走了过来。我…

Leetcode:701. 二叉搜索树中的插入操作(C++)

目录 问题描述&#xff1a; 实现代码与解析&#xff1a; 递归&#xff1a; 原理思路&#xff1a; 迭代&#xff1a; 原理思路&#xff1a; 问题描述&#xff1a; 给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和要插入树中的值 value &#xff0c;将值插入二…

Codeforces Round #843 (Div. 2)——A,B,C,E

​​​​​​​​​​​Dashboard - Codeforces Round #842 (Div. 2) - Codeforces A: 思维构造 题意&#xff1a;给定一个由 ab 组成的字符串&#xff0c;将该字符串拆分成 3 个部分&#xff08;a&#xff0c;b&#xff0c;c&#xff09;&#xff0c;要求中间部分的字典序最大…

2022 年终总结

在 12 月 31 号晚上这天&#xff0c;打开朋友圈大家都在告别 2022、迎接 2023&#xff0c;我却想不到任何值得发的内容。没有外出体会元旦的节日氛围&#xff0c;也没有观看任何跨年活动&#xff0c;2022 年最后一秒跟全年的 3153.6 万秒没有任何区别。 甚至这篇总结都差点没有…

RK3568源码编译与交叉编译环境搭建

本篇进行飞凌OK3568-C开发板的Linux系统开发需要用的软件交叉编译环境的配置。 对于软件开发&#xff0c;如果只是使用C/C代码&#xff0c;则在自己的Ubuntu虚拟机中添加RK3568对应的交叉编译器(gcc/g)即可&#xff0c;如果要进行Qt开发&#xff0c;则还要再交叉编译与RK3568配…

UDS诊断系列介绍09-1485服务

本文框架1. 系列介绍1.1 14服务概述1.2 85服务概述2. 14服务请求与应答2.1 14服务请求2.2 14服务正响应3. 85服务请求与应答3.1 85服务请求3.2 85服务正响应3.3 否定应答4. Autosar系列文章快速链接1. 系列介绍 UDS&#xff08;Unified Diagnostic Services&#xff09;协议&a…

graalvm+spring-cloud-gateway打造又快又小的类nginx本地网关

前言 网关是微服务架构的入口&#xff0c;外网请求通过网关转发到独立的微服务。项目一般会经过多个环境的测试&#xff0c;最终发布到生产。一个http请求&#xff0c;如&#xff1a;http://public_host/api/v1/some_service/some_path?ab&cd会先经过公网域名&#xff0c…

ThinkPHP5.x未开启强制路由(s参数)RCE

官方公告&#xff1a;https://blog.thinkphp.cn/869075 由于框架对控制器名没有进行足够的检测会导致在没有开启强制路由的情况下可能的getshell漏洞&#xff0c;受影响的版本包括5.0和5.1版本 ThinkPHP5基础 环境搭建 官网直接下载完整包 https://www.thinkphp.cn/down/870.…

ElasticSearch集群架构及底层原理

前言ElasticSearch考虑到大数据量的情况&#xff0c;集群有很多的部署模式&#xff0c;本篇不会具体进行演示了&#xff0c;只是说明一下有哪些架构可以选&#xff0c;及一些原理的简单介绍&#xff0c;如果要看具体操作的那么可以自行进行搜索&#xff0c;这不是本篇博客要介绍…

OCR文字识别软件哪个好?7大文字识别软件

由于从各种文档中提取文本的需求非常普遍&#xff0c;许多办公软件或公司都提供了OCR工具。在本文中&#xff0c;我们为您推出了一系列功能强大且易于使用的最佳 OCR 软件。 什么是 OCR 软件&#xff1f; OCR 软件是一种程序或工具&#xff0c;可以使用光学字符识别技术识别数…

小红书数据分析网站:揭晓普通博主1个月涨粉百万的密码!

导语&#xff1a; 随着2023年的来临&#xff0c;回首小红书动态&#xff0c;行业热度依旧高涨&#xff0c;越来越多的达人涌入小红书。在时尚领域&#xff0c;更是出现了如氧化菊这样的大势变装博主&#xff01;短短一周涨粉13W的变装博主为何能突围&#xff0c;强势吸睛呢&am…

[LCTF]bestphp2022安洵杯 babyphp

目录 <1> [LCTF]bestphp‘s revenge SoapClient触发反序列化导致ssrf serialize_hander处理session方式不同导致session注入 crlf漏洞 <2> 安洵杯 babyphp SoapClient 触发ssrf session反序列化 利用文件操作原生类读取flag <3> XCTF Final Web1 解…

Spring Security 解析(六) —— 基于JWT的单点登陆(SSO)开发及原理解析

Spring Security 解析(六) —— 基于JWT的单点登陆(SSO)开发及原理解析 在学习Spring Cloud 时&#xff0c;遇到了授权服务oauth 相关内容时&#xff0c;总是一知半解&#xff0c;因此决定先把Spring Security 、Spring Security Oauth2 等权限、认证相关的内容、原理及设计学习…

[极客大挑战 2019]Secret File

目录 信息收集 解题思路 信息收集 先看源码&#xff0c;发现一个php文件 <a id"master" href"./Archive_room.php" style"background-color:#000000;height:70px;width:200px;color:black;left:44%;cursor:default;">Oh! You found me&…

9.2 容器库概览

文章目录所有容器的共性&#xff1a;迭代器迭代器的范围容器类型成员begin和end成员容器的定义和初始化与顺序容器大小相关的构造函数赋值和swapassignedswap容器大小操作关系运算符所有容器的共性&#xff1a; 表格一&#xff1a; 类型别名说明iterator迭代器const_iterator…

用R语言理解全微分

文章目录6 全微分梯度的概念全微分前情提要 R语言微积分极限π,e,γ\pi, e, \gammaπ,e,γ洛必达法则连续性和导数数值导数差商与牛顿插值方向导数 6 全微分 梯度的概念 对于任意函数f(x0,x1,⋯,xn)f(x_0,x_1,\cdots,x_n)f(x0​,x1​,⋯,xn​)&#xff0c;其梯度为 ∇f(∂f∂…

解决从BIOS选择从U盘启动但是系统仍然从硬盘启动的问题

我怀疑是BIOS失去了记忆能力&#xff0c;不能记住我的选择&#xff0c;所以仍然按默认从硬盘启动。 解决&#xff1a;重置BIOS即可 下面用物理方法重置BIOS。 在主板上找到这三根针&#xff0c;将上面的黑色套子拔出&#xff0c;然后插入旁边的另外两根针&#xff0c;例如开始…

基于python知识图谱医疗领域问答系统实现

直接上结果展示: “让人类永远保持理智,确实是一种奢求” ,机器人莫斯,《流浪地球》 项目概况 本项目为一个使用深度学习方法解析问题,知识图谱存储、查询知识点,基于医疗垂直领域的对话系统的后台程序 运行效果:

aws beanstalk 结合packer创建自定义平台

参考资料 https://github.com/aws-samples/eb-custom-platforms-samples#updating-packer-templateElastic Beanstalk 自定义平台 今天使用eb平台创建环境的时候&#xff0c;发现有名为packer的选项&#xff0c;查询文档发现aws beanstalk支持自定义平台&#xff0c;这功能几…

4. 使用预训练的PyTorch网络进行图像分类

4. 使用预训练的PyTorch网络进行图像分类 这篇博客将介绍如何使用PyTorch预先训练的网络执行图像分类。利用这些网络只需几行代码就可以准确地对1000个常见对象类别进行分类。这些图像分类网络是开创性的、最先进的图像分类网络&#xff0c;包括VGG16、VGG19、Inception、Dens…