多进程实现:从 “老板招人” 到 “团队干活” 的打工人指南(诙谐复习版)
家人们!多进程理论是 “职场规则”,那多进程实现就是 “招人造团队” 的实操手册 —— 今天咱们把fork当 “招聘工具”,exit当 “离职流程”,wait当 “交接手续”,用打工人的故事把知识点嚼碎,以后复习看一眼就懂!
一、核心操作 1:招新员工 ——fork函数(进程创建)
要搞多进程,第一步得 “招人”,fork就是系统给的 “招聘按钮”,文档里说它 “通过拷贝父进程得到子进程”,翻译成人话:老板(父进程)点一下 “招人”,立马复制出一个跟自己一模一样的新员工(子进程),连手里的工作流程(代码执行位置)都同步!
1. 招聘工具用法(函数原型)
c
运行
#include <unistd.h>
pid_t fork(void); // 无参数=招人不用填申请表,点了就出结果
关键看返回值 —— 这是 “招聘结果通知”,三种情况要记死:
- 父进程里:返回新员工的 “工号(PID)”—— 老板手里捏着工号,方便管新员工;
- 子进程里:返回
0—— 新员工刚入职,还没自己的工号(0表示 “我是小弟”); - 失败时:返回
-1—— 招聘失败(比如公司资源满了,招不下了),还会触发errno(招聘失败原因)。
2. 招聘的 “神奇细节”:写时复制
新员工入职时,不会立马抄老板所有资料(内存)—— 先共用老板的文件、代码,等新员工要改自己的资料(比如修改变量),系统才会复制一份给它(写时复制)。就像:老板有份工作手册,新员工先借来看,要改笔记时再自己印一本,省纸又高效~
3. 避坑:招人后必须 “分工”
新手最容易犯的错:点了fork却不判断返回值,导致老板和新员工干同一件事(比如都往文件里写内容,互相覆盖)。记住:fork之后一定要用返回值 “分岔”,让老板和新员工走不同流程!
举个栗子(文档经典案例改编):
c
运行
#include <stdio.h>
#include <unistd.h>
int main() {
printf("老板:准备招人干活啦!\n"); // 招人前的活,只有老板干
pid_t pid = fork(); // 点下招聘按钮
if (pid > 0) {
// 老板的活:管新员工
printf("老板:新员工工号是%d,我继续干我的活~\n", pid);
} else if (pid == 0) {
// 新员工的活:干分配的任务
printf("新员工:我入职啦!我的工号是%d(getpid()查的)\n", getpid());
} else {
perror("老板:招聘失败!(fork error)"); // 打印失败原因
}
return 0;
}
运行结果:老板和新员工各干各的,完美分工~
二、核心操作 2:查工号 ——getpid/getppid(进程 ID 查询)
新员工入职后,总得知道 “自己的工号” 和 “老板的工号” 吧?这俩函数就是 “工号查询机”,文档说它们 “获取当前进程 / 父进程 PID”,用法简单到离谱:
1. 查号工具用法(函数原型)
c
运行
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); // 查自己的工号(新员工查自己)
pid_t getppid(void); // 查老板的工号(新员工查上级)
无参数!调用就返回工号,比如新员工想确认上级:
c
运行
#include <stdio.h>
#include <unistd.h>
int main() {
fork(); // 招新员工
if (getpid() != getppid()) { // 新员工(自己工号≠老板工号)
printf("新员工:我的工号%d,老板工号%d\n", getpid(), getppid());
}
return 0;
}
运行结果:新员工精准查到自己和老板的工号,再也不怕认错上级~
三、核心操作 3:办离职 ——exit/_exit(进程退出)
新员工干活干完了,得 “离职”,文档给了两种方式:exit(体面离职)和_exit(任性跑路),核心区别是 “清不清理工位(缓冲区)”。
1. 两种离职方式对比(打工人版)
| 函数 | 类型 | 核心区别(清理工位) | 场景举例 |
|---|---|---|---|
exit |
库函数 | 会刷新标准 IO 缓冲区 | 干完活收拾好工位再走 |
_exit |
系统调用 | 不刷新缓冲区 | 紧急情况直接溜,工位乱 |
函数原型(文档对应):
c
运行
#include <stdlib.h>
void exit(int status); // status=离职状态(0=正常,非0=异常)
#include <unistd.h>
void _exit(int status); // 同上,但不收拾工位
2. 离职状态的 “小秘密”
status参数不是直接给老板的 —— 系统会自动做个 “裁剪”:最终老板拿到的是status & 0377(只保留低 8 位)。就像新员工写离职报告,老板只看最后一句总结(低 8 位),前面的废话全忽略~
3. 避坑:别用_exit丢东西
如果新员工用printf写了 “工作成果”(内容在缓冲区里),用_exit离职会导致成果没保存(工位没收拾,东西丢了)!比如:
c
运行
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
printf("新员工:我干完活啦~"); // 没加\n,内容在缓冲区
// exit(0); // 体面离职:会刷新缓冲区,老板能看到这句话
_exit(0); // 任性跑路:缓冲区没刷,老板看不到这句话
}
记住:想让成果不丢,用exit;紧急错误要快退,用_exit~
四、核心操作 4:办交接 ——wait/waitpid(资源回收)
新员工离职后,老板得 “办交接”(回收它占的资源:PID、内存等),不然新员工会变成 “僵尸员工”(占着工位不撒手,导致公司招不了新人)。文档说wait和waitpid是 “资源回收函数”,就是老板的 “交接手册”。
1. 基础交接:wait函数(死等第一个离职员工)
c
运行
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status); // status=存离职员工的状态
- 功能:老板停下手里的活,死等第一个新员工离职,办完交接再继续;
- 返回值:成功返回离职员工的工号,失败返回
-1; - status 参数:不想管离职原因就填
NULL,想管就传数组地址(用WEXITSTATUS(status)取离职状态)。
栗子(文档练习思路):
c
运行
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork(); // 招新员工
if (pid > 0) {
printf("老板:等新员工干完活交接...\n");
int status;
wait(&status); // 死等交接
printf("老板:新员工离职状态是%d(0=正常)\n", WEXITSTATUS(status));
} else if (pid == 0) {
sleep(3); // 新员工干3秒活
printf("新员工:我离职啦~\n");
exit(0); // 正常离职(状态0)
}
return 0;
}
2. 灵活交接:waitpid函数(指定员工 / 边干活边等)
如果老板招了多个新员工,wait只能等第一个,这时候需要waitpid(灵活版交接手册):
c
运行
pid_t waitpid(pid_t pid, int *status, int options);
三个参数全是 “灵活开关”,记牢常用值:
- pid 参数:指定等哪个员工:
>0:等工号是这个值的员工(指定人交接);-1:等任意员工(跟wait一样);
- options 参数:交接方式:
0:阻塞等(老板停下干活,专心等);WNOHANG:非阻塞等(老板边干活边等,没人离职就继续干)。
栗子(边干活边等):
c
运行
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid > 0) {
while (1) {
int status;
pid_t ret = waitpid(pid, &status, WNOHANG); // 边干活边等
if (ret > 0) {
printf("老板:新员工%d交接完成!\n", ret);
break;
} else if (ret == 0) {
printf("老板:继续干我的活,新员工还在忙...\n");
sleep(1);
}
}
} else if (pid == 0) {
sleep(3);
exit(0);
}
return 0;
}
运行结果:老板每隔 1 秒看一眼,直到新员工离职,不耽误自己干活~
五、实战:团队协作干活 —— 多进程文件拷贝(文档重点案例)
学完工具,得搞实战!文档里的 “多进程拷贝文件” 超经典:老板(父进程)拷贝前半段,新员工(子进程)拷贝后半段,效率翻倍~
1. 团队分工思路
- 老板先打开源文件和目标文件(只开一次,避免新员工重复打开清空文件);
- 算好文件大小(分一半,比如 1000 字节的文件,老板干 0-499,新员工干 500-999);
- 招新员工(
fork),新员工继承文件描述符(不用重开); - 老板和新员工分别拷贝自己的部分;
- 老板等新员工交接(
wait),最后一起关文件。
2. 代码实现(带文档溯源)
c
运行
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/stat.h>
// 查文件大小(文档2-215思路)
off_t get_file_size(int fd) {
off_t size = lseek(fd, 0, SEEK_END); // 光标移末尾,偏移量=文件大小
lseek(fd, 0, SEEK_SET); // 光标复位,不影响后续拷贝
return size;
}
// 拷贝指定范围(文档2-216思路)
int copy_range(int srcfd, int destfd, off_t start, off_t len) {
lseek(srcfd, start, SEEK_SET); // 源文件光标移到起始位置
lseek(destfd, start, SEEK_SET); // 目标文件光标同步
char buf[1024]; // 数据搬运箱
off_t remaining = len; // 剩余要拷贝的字节数
while (remaining > 0) {
// 每次搬的量:不超过搬运箱大小,也不超过剩余量
size_t read_len = (sizeof(buf) < remaining) ? sizeof(buf) : remaining;
read_len = read(srcfd, buf, read_len); // 从源文件搬数据
if (read_len == -1) return -1;
write(destfd, buf, read_len); // 往目标文件搬数据
remaining -= read_len; // 更新剩余量
}
return 0;
}
int main(int argc, char *argv[]) {
// 检查参数:必须传“源文件”和“目标文件”
if (argc != 3) {
fprintf(stderr, "用法:%s 源文件 目标文件\n", argv[0]);
return -1;
}
// 老板打开文件(文档2-233思路:只开一次)
int srcfd = open(argv[1], O_RDONLY); // 源文件:只读
int destfd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644); // 目标文件:创建+清空
if (srcfd == -1 || destfd == -1) { perror("open error"); return -1; }
off_t file_len = get_file_size(srcfd); // 算文件大小
pid_t pid = fork(); // 招新员工
if (pid > 0) {
// 老板拷贝前一半(文档2-239思路)
copy_range(srcfd, destfd, 0, file_len / 2);
wait(NULL); // 等新员工交接
printf("拷贝完成!老板收尾~\n");
} else if (pid == 0) {
// 新员工拷贝后一半(文档2-245思路)
copy_range(srcfd, destfd, file_len / 2, file_len - file_len / 2);
exit(0); // 新员工离职
} else {
perror("fork error(招聘失败)");
}
close(srcfd); // 老板关文件
close(destfd);
return 0;
}
3. 实战避坑(文档强调点)
- 目标文件别让新员工重开:新员工如果再用
O_TRUNC打开,会清空老板刚拷贝的内容(新员工别乱删老板的活); - 用
off_t存文件大小:int存不下大文件(比如 2GB 以上),off_t是专门存文件大小的类型(能搬大箱子); - 必须
wait:不然新员工离职后变僵尸(占着 PID,公司招不了新人)~
六、复习口诀(30 秒记完核心)
- fork 招人:fork 返回两结果,父得 PID 子得 0,失败负一要查错;
- 查号工具:getpid 查自己,getppid 找上级,无参调用超容易;
- 离职流程:exit 体面清缓冲,_exit 快退不清理,status 低 8 位有用;
- 交接手续:wait 死等任意子,waitpid 灵活指定之,WNOHANG 不阻塞;
- 实战关键:父子分工先算 len,文件只开一次行,交接完成再关柄。
小贴士
多进程实现的核心就是 “招人(fork)→ 分工(判 pid)→ 干活(业务逻辑)→ 离职(exit)→ 交接(wait)”,就像老板带团队的完整流程。复习时想不起来,就把自己代入 “老板”,想想怎么招人造团队干活,知识点立马就通了~ 下次遇到多进程问题,先画 “老板 - 新员工” 分工图,思路绝对不乱!
多进程实现复习速查表(怕忘就看这个!)
| 操作 | 核心作用 | 关键语法 / 避坑点 |
|---|---|---|
| 进程创建(fork) | 拷贝父进程生成子进程,搭建多进程 “团队” |
1. 语法: 2. 返回值:父进程得子进程 PID(>0)、子进程得 0、失败得 - 1; 3. 避坑:必须通过返回值分工(否则父子干同活),子进程共享父进程资源(写时复制,改数据才拷贝) |
| 进程 ID 查询(getpid/getppid) | 查询当前进程 “工号”(PID)/ 父进程 “上级工号”(PPID) |
1. 语法: 2. 避坑:无参数直接调用,子进程父进程退出后,PPID 会变为 1(被 init 进程收养) |
| 进程退出(exit/_exit) | 终止进程,释放 “工位资源” |
1. 语法:- 2. 避坑: |
| 资源回收(wait/waitpid) | 回收子进程资源,避免 “僵尸员工”(僵尸进程) |
1. 语法: 2. 避坑:- |
| 多进程分工协作(实战重点) | 父子进程协同完成任务(如文件拷贝) |
1. 核心逻辑:父进程打开资源(如文件)→ fork 创建子进程→ 按返回值分工(如父拷前半、子拷后半)→ 父进程回收子进程; 2. 避坑:- 共享资源(如文件描述符)仅父进程打开一次(子进程继承,避免重复打开清空);- 大文件用 |
更多推荐
所有评论(0)