网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
当用父子进程之间创建管道来通信时:
上述的管道特点、匿名管道的原理等。都可以使用下面的代码来测试(代码有超详细注释)
下面这段代码创建了一个简单的单向通信的管道
#include <iostream>
#include <unistd.h> // 操作系统调用接口, 只能 .h 结尾(规定的)
#include <cassert> // 以 c 开头 + C语言头文件(不加 h) 就可以重新封装一套 C 语言头文件 #include<cstdio> #include<cstring>
#include <sys/types.h>
#include <cstdio>
#include <cstring>
#include <sys/wait.h>
using namespace std;
#define MAX 1024
int main()
{
int pipefd[2] = {0};
// 第一步:建立管道
int n = pipe(pipefd); // 管道创建成功 等于 0 创建成功,创建失败返回 -1
assert(n == 0); // 只在 debug 下存在 assert 用于意料之外的问题!
(void)n; // 假装使用一下, 防止 n 没有被使用而报警
// cout << pipefd[0] << " " << pipefd[1] << endl; // 使用 pipe 函数后 pipe[0], pipe[1] 分别自动被设置为 3,4 (未被使用的两个最小的文件描述符)
// pipefd[0] 读端 pipefd[1] 写端
// 第二步:创建子进程
pid_t id = fork();
if (id < 0)
{
// 创建失败
perror("fork");
return 1;
}
// 第三步:父子关闭不需要的文件描述符,形成单向通信的信道
// 子进程写入,父进程读取
if (id == 0)
{
// 子进程
close(pipefd[0]);
int cnt = 10;
// 只向管道写入
while(cnt)
{
char message[MAX];
snprintf(message, sizeof(message) - 1, "Hello father, I am child, pid: %d, cut: %d", getpid(), cnt);
cnt--;
write(pipefd[1], message, strlen(message));
sleep(1);
}
// TODO
exit(0);
}
// 父进程
close(pipefd[1]); // 父进程关闭写端
char buffer[MAX];
while(true)
{
ssize_t n = read(pipefd[0], buffer, sizeof(buffer) - 1); // 一会要给最后一个位置加 '\0',方便 C/C++ 使用(万一读满了, 就最多读 MAX - 1 个)
// 面向字节流读取, 跟写的时候的格式无关, 读取的时候只跟字节数有关
if(n > 0)
{
buffer[n] = 0; // 自己维护字符串(为 C++ 准备的)
cout << getpid() << " :child say:" << buffer << " to me !" <<endl;
}
if(n == 0) break; // 说明写端关闭了(不是写端不写,而是关闭了)读端就会关闭
}
// 子进程退出后才能等待到
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid == id)
{
// 等待成功
cout << "wait success, child exit sig: " << (status &0x7F) << endl; // 退出信号 :status 的低 7 位
}
return 0;
}
使用管道技术设计一个简单的进程池
**进程池(Process Pool)**是一种并发编程的技术,它允许创建一组预先分配的子进程,这些子进程可以被重复地使用来执行任务。
通常情况下,每个进程都会拥有一个独立的地址空间和资源,这会导致进程的创建和销毁需要耗费大量的时间和资源。而进程池技术通过预先创建一组子进程,这些子进程会在一个池中等待任务分配。当有任务需要执行时,只需要将任务交给其中一个空闲的子进程即可,不需要重复地创建和销毁进程。
进程池通常由一个主进程(也称为管理进程)和一组子进程组成。主进程负责管理子进程的创建、销毁和任务分配,而子进程则负责执行实际的任务。
进程池的主要优势:提高效率、简化编程、提高可扩展性,进程池主要适用于CPU密集型的任务,即任务主要涉及到计算和数据处理。
#include <iostream>
#include <cassert>
#include <unistd.h>
#include <functional>
#include <ctime>
#include <vector>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>
typedef std::function<void()> task_t;
void Download()
{std::cout << "我是一个下载任务" << std::endl;}
void PrintLog()
{std::cout << "我是一个打印日志的任务" << std::endl;}
void PushVideoStream()
{std::cout << "这是一个推送视频流的任务" << std::endl;}
// void ProcessExit()
// {exit(0);}
class Init
{
public:
// 任务码
const static int g_download_code = 0;
const static int g_printlog_code = 1;
const static int g_pushVideostream_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()
{
int code = rand() % tasks.size();
return code;
}
std::string ToDesc(int code) //将任务码转化为任务字符传串
{
switch (code)
{
case g_download_code:
return "g_download_code";
case g_printlog_code:
return "g_printlog_code";
case g_pushVideostream_code:
return "g_pushVideostream_code";
default:
return "Unknow";
}
}
};
Init init; // 定义对象
const int num = 5;
static int number = 1;
class channel // 先描述, 再组织
{
public:
channel(int fd, pid_t id)
: ctrlfd(fd),
workerid(id)
{
name = "channel-" + std::to_string(number++);
}
public:
int ctrlfd;
pid_t workerid;
std::string name;
};
void Work()
{
while (true)
{
int code = 0;
ssize_t n = read(0, &code, sizeof(code)); // n 表示每次读到的字节个数
if (n == 0) break; // 等于0 说明啥都没读到退出子进程并回收
(void)n;
if (!init.CheckSafe(code))
continue; // code 不合法, 不在任务范围之内
init.RunTask(code);
}
}
void PrintDebug(const std::vector<channel> &c)
{
for (const auto &channel : c)
{
std::cout << channel.ctrlfd << "," << channel.workerid << "," << channel.name << std::endl;
}
}
void CreateChannels(std::vector<channel> *c)
{
std::vector<int> old; // 用来保存父进程的写端文件描述符
for (int i = 0; i < num; i++)
{
// 1. 先创建管道
int pipefd[2];
int n = pipe(pipefd);
assert(n == 0);
(void)n;
// 2. 创建子进程
pid_t id = fork();
assert(id != -1);
// 3. 构建单向信道
if (id == 0) // child
{
if(!old.empty())
{
for(int i = 0; i < old.size(); i++)
{
close(old[i]); // 关闭继承过来的父进程的文件描述符
}
}
close(pipefd[1]);
dup2(pipefd[0], 0); // 管道直接切换为从标准输入中读(重定向)
Work();
exit(0);
}
// father
close(pipefd[0]);
old.push_back(pipefd[1]); //保存这次进程的写端, 方便下次的子进程关闭这些从父进程中继承过来的不需要要的端口
c->push_back(channel(pipefd[1], id));
// std::cout << id << std::endl;
}
}
void SendCommand(std::vector<channel> &c, bool flag, int num = 0)
{
![img](https://img-blog.csdnimg.cn/img_convert/8ab7bdfae1e15fba1936a25981b58ff9.png)
![img](https://img-blog.csdnimg.cn/img_convert/f3d46a5eb612322c30928b6d9a5ca7b0.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**
h2MUB-1715566861781)]
[外链图片转存中...(img-XDiBjGEQ-1715566861781)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**