家人们!多进程理论是 “职场规则”,那多进程实现就是 “招人造团队” 的实操手册 —— 今天咱们把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、内存等),不然新员工会变成 “僵尸员工”(占着工位不撒手,导致公司招不了新人)。文档说waitwaitpid是 “资源回收函数”,就是老板的 “交接手册”。

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. 团队分工思路

  1. 老板先打开源文件和目标文件(只开一次,避免新员工重复打开清空文件);
  2. 算好文件大小(分一半,比如 1000 字节的文件,老板干 0-499,新员工干 500-999);
  3. 招新员工(fork),新员工继承文件描述符(不用重开);
  4. 老板和新员工分别拷贝自己的部分;
  5. 老板等新员工交接(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 秒记完核心)

  1. fork 招人:fork 返回两结果,父得 PID 子得 0,失败负一要查错;
  2. 查号工具:getpid 查自己,getppid 找上级,无参调用超容易;
  3. 离职流程:exit 体面清缓冲,_exit 快退不清理,status 低 8 位有用;
  4. 交接手续:wait 死等任意子,waitpid 灵活指定之,WNOHANG 不阻塞;
  5. 实战关键:父子分工先算 len,文件只开一次行,交接完成再关柄。

小贴士

多进程实现的核心就是 “招人(fork)→ 分工(判 pid)→ 干活(业务逻辑)→ 离职(exit)→ 交接(wait)”,就像老板带团队的完整流程。复习时想不起来,就把自己代入 “老板”,想想怎么招人造团队干活,知识点立马就通了~ 下次遇到多进程问题,先画 “老板 - 新员工” 分工图,思路绝对不乱!

多进程实现复习速查表(怕忘就看这个!)

操作 核心作用 关键语法 / 避坑点
进程创建(fork) 拷贝父进程生成子进程,搭建多进程 “团队”

1. 语法:#include <unistd.h> + pid_t fork(void)

2. 返回值:父进程得子进程 PID(>0)、子进程得 0、失败得 - 1;

3. 避坑:必须通过返回值分工(否则父子干同活),子进程共享父进程资源(写时复制,改数据才拷贝)

进程 ID 查询(getpid/getppid) 查询当前进程 “工号”(PID)/ 父进程 “上级工号”(PPID)

1. 语法:#include <sys/types.h>+#include <unistd.h>pid_t getpid(void)(查自己)、pid_t getppid(void)(查父进程);

2. 避坑:无参数直接调用,子进程父进程退出后,PPID 会变为 1(被 init 进程收养)

进程退出(exit/_exit) 终止进程,释放 “工位资源”

1. 语法:- exit#include <stdlib.h> + void exit(int status)(刷新缓冲区,体面退出);- _exit#include <unistd.h> + void _exit(int status)(不刷新缓冲区,紧急退出);

2. 避坑:_exit会丢缓冲区数据(如 printf 未刷新),status仅低 8 位有效(用WEXITSTATUS提取)

资源回收(wait/waitpid) 回收子进程资源,避免 “僵尸员工”(僵尸进程)

1. 语法:#include <sys/wait.h>,- waitpid_t wait(int *status)(阻塞等任意子进程,填 NULL 不关心退出状态);- waitpidpid_t waitpid(pid_t pid, int *status, int options)

2. 避坑:- pid:>0 指定子进程 PID、-1 等任意子进程;- options:0 阻塞、WNOHANG非阻塞;- 必须回收,否则子进程变僵尸(占 PID 资源)

多进程分工协作(实战重点) 父子进程协同完成任务(如文件拷贝)

1. 核心逻辑:父进程打开资源(如文件)→ fork 创建子进程→ 按返回值分工(如父拷前半、子拷后半)→ 父进程回收子进程;

2. 避坑:- 共享资源(如文件描述符)仅父进程打开一次(子进程继承,避免重复打开清空);- 大文件用off_t存大小(避免int溢出);- 分工后子进程需exit,父进程需wait

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐