资讯动态

操作系统实验指南:进程、线程、信号量与管道通信实战

发布时间:2026/10/23 6:42:40 来源:尧图企业网站定制
简介西安电子科技大学操作系统课程实验报告聚焦进程管理、线程同步与进程间通信三大核心实验适合操作系统课程学生、Linux初学者及准备课程设计或复试的读者作为参考。报告按六个实验依次展开进程创建与父子进程协同、线程共享进程数据、信号异步通信、匿名管道与命名管道通信、信号量实现临界区互斥。每个实验均包含可运行的C语言代码、程序逻辑分析、运行截图及心得体会涉及fork、waitpid、pthread_create、signal、kill、sem_init等关键接口并能直观展示父子进程等待机制、线程全局变量共享及多种IPC方式的异同。压缩包内共1个doc文档整体大小仅377KB内容紧凑便于阅读和打印。目前已有1350人学习下载适合用来对照实验要求查漏补缺、快速梳理Linux环境下操作系统核心机制也便于复试或期末复习时重点回顾。1. 零基础先搞懂这份实验报告到底讲什么很多第一次接触操作系统的同学翻开教材看到“进程”“线程”“信号量”这些词第一反应是这跟我平时用电脑有什么关系其实关系大得很。你手机里同时开着微信、音乐、浏览器它们能互不打扰地运行你写文档时后台还在下载文件前台也不卡顿——这些都是操作系统在背后管理“并发执行”的结果。这份实验报告就是带着你用最朴素的方式把这些“幕后管理”亲手做一遍。整套实验在 Linux 环境下用 C 语言完成一共六个实验路径非常清晰先学会创建进程和线程再学会让进程之间传递消息最后用信号量解决“多个进程抢资源”的经典难题。换句话说它把操作系统里最难啃的“并发与通信”拆成了一个个能跑、能看结果的小程序。你不需要先啃完一整本教材跟着实验一个个做下来概念自然就通了。适合正在上操作系统课、或者想动手验证理论的同学。2. 先迈第一步进程到底是什么怎么创建第一个进程2.1 用生活类比理解进程厨房里的“一条做菜流水线”进程这个概念第一次听会觉得抽象。你把它想成厨房里的一条做菜流水线每个进程就是一条独立的流水线有自己的灶台、自己的锅碗瓢盆内存空间互不干扰。操作系统就是厨房总管负责安排哪条流水线什么时候开工、什么时候暂停。而程序本身只是“菜谱”放在硬盘上不动一旦你运行它系统就按菜谱开出一条流水线这才叫进程。同一个菜谱可以同时开多条流水线也就有了“同一个程序多个进程”的情况。实验一让你做的事用厨房类比就是你父进程招了一个帮工子进程让帮工去读一份菜谱文件你站在旁边等等他读完你再继续干活。这里最关键的两个动作是“招人”和“等人”。2.2 动手写第一个代码fork() 创建子进程 waitpid() 等待实验一的完整代码是这样我加上了详细注释#include sys/unistd.h #include sys/wait.h #include unistd.h #include stdio.h #include stdlib.h int main() { pid_t fpid fork(); // 调用fork系统创建一个子进程 int count 0; if (fpid 0) { printf(error\n); // fork失败一般是因为系统资源不够 } else if (fpid 0) { // 子进程进入这个分支fpid为0 printf(子进程我是子进程,id%d开始读取文件\n, getpid()); FILE *fp fopen(test1file.txt, r); // 打开文件 if (fp NULL) { printf(未找到文件\n); } else { char text[2000]; while (fgets(text, 2000, fp) ! NULL) { puts(text); // 一行行输出文件内容 } } printf(子进程读取文件结束\n); exit(1); // 子进程结束 } else { // 父进程进入这个分支fpid为子进程的pid printf(父进程我是父进程id%d ,我要等待子进程\n, getpid()); int ret waitpid(0, NULL, 0); // 父进程在这里等子进程结束 printf(父进程子进程已经结束ret%d\n, ret); } return 0; }这段代码最神奇的地方在fork()调用一次却返回两次。系统把当前进程完整复制了一份两份代码从fork()之后继续跑区别只在于返回值——父进程拿到的是子进程的 ID子进程拿到的是 0。waitpid(0, NULL, 0)是“等人”的关键父进程执行到这行会一直阻塞直到任何一个子进程结束才继续往下走。这在进程协同里特别重要不然父进程可能跑完了子进程还没读完文件。2.3 怎么编译运行以及运行结果怎么看在 Linux 终端里按下面几步操作# 1. 先把程序保存成文件比如 process.c # 2. 用gcc编译-o参数指定输出文件名 gcc process.c -o process # 3. 先创建子进程要读的文件 echo hello, this is test file test1file.txt # 4. 运行程序 ./process我第一次跑这个实验时最困惑的是printf明明有两个分支都写了输出为什么终端里看起来像是“父进程先打了一行然后子进程打了好几行”其实这不是乱序而是waitpid让父进程乖乖等在了原地。你看运行结果父进程我是父进程id1001 ,我要等待子进程 子进程我是子进程id1002开始读取文件 hello, this is test file 子进程读取文件结束 父进程子进程已经结束ret1002这个顺序稳定出现正是因为waitpid把父进程按住了。你可以在实验里故意把waitpid那行注释掉再跑一次会发现父进程不等子进程自己先打印完就结束了——这就是“协同”和“不协同”的差别值得亲手验证一次。::: tip 提示 fork() 之后父子进程的代码是相同的靠fpid 0和fpid 0来分流。这是初学者最容易绕晕的地方记住一句话fork 之后有两个执行流在跑同一个程序。 :::3. 进程学会还不够线程和进程到底差在哪3.1 为什么有了进程还要学线程进程虽然能隔离任务但代价不小每个进程都有独立的内存空间想共享数据就得靠管道、消息队列这些通信机制麻烦。现实中很多任务需要“多个执行流协同做同一件事”比如一个视频播放器一边解码、一边渲染、一边处理用户点击——它们共享同一份视频数据如果拆成多个进程数据传来传去会慢得没法用。这时候就轮到线程出场。一个厨房里进程是整条流水线线程就是流水线上的几个工人共用同一口锅、同一块案板同一份内存配合起来效率高得多。操作系统实验报告里第二个实验就是让你亲自感受这种数据共享。3.2 线程实验代码详解pthread_create() 修改全局变量实验二的完整代码#include stdio.h #include stdlib.h #include pthread.h #include unistd.h int shdata 0; // 全局共享变量线程和进程都能访问 void *creat(void *arg) // 线程要执行的函数 { printf(new pthread ....\n); printf(shared data %d \n, shdata); shdata; // 在线程中修改共享变量 printf(after operating :shared data %d\n, shdata); return (void *)0; } int main() { pthread_t tid; // 线程标识符 int pth pthread_create(tid, NULL, creat, NULL); // 创建线程 if (pth ! 0) { printf(creat thread error\n); return -1; } else { sleep(3); // 主线程睡3秒等新线程执行完 printf(creat thread success\n); printf(shared data %d \n, shdata); // 主线程再来看全局变量 return 0; } }pthread_create的四个参数值得记一下第一个是线程标识符的地址第二个是线程属性一般填NULL用默认值第三个是线程要执行的函数名第四个是传给线程函数的参数这里用不到就填NULL。这个实验最直观的点在于全局变量shdata在线程里被了主线程 sleep 之后再打印看到的已经是修改后的值。这跟进程完全不一样——如果是两个进程各自有一份变量副本子进程改了父进程根本看不见。这就是“线程共享进程数据”的现场演示。3.3 数据共享的代价为什么实验里要 sleep(3)代码里主线程 sleep(3) 不是随便写的它有讲究。线程创建后是“就绪”状态什么时候被调度执行由操作系统决定。主线程如果不睡可能先执行完printf(shared data %d, shdata)那时线程还没跑打印出来还是 0实验就“翻车”了。sleep(3) 是偷懒但有效的同步方式让出 CPU给新线程时间完成修改。实际开发中不会这么干而是用pthread_join等待线程结束但在入门实验里先用 sleep 理解“线程确实改了数据”这个事实更重要。你可以做个小试验把 sleep(3) 改成 sleep(0)多跑几次大概率会看到主线程先打印出 0。这能帮你直观理解什么叫“线程调度顺序不确定”——这是并发编程里最需要习惯的一件事。::: tip 注意 编译线程程序时要加-lpthread参数链接线程库比如gcc thread.c -o thread -lpthread否则会报“找不到 pthread_create”的链接错误。 :::4. 核心实战让进程之间说上话——信号、匿名管道、命名管道4.1 先搞懂一个问题进程之间为什么要通信两个进程各有各的内存空间你在进程 A 里定义一个变量进程 B 完全看不到。但现实里的任务往往是协作的比如一个进程负责下载数据另一个进程负责处理数据下载完的得告诉处理的一声“数据准备好了你来取。”这就牵涉到进程间通信IPC, Inter-Process Communication。实验三到实验五就是带你把三种最基础的通信方式都亲手用一遍信号像“按门铃”只能通知“有事发生”传不了复杂内容匿名管道像“把一根水管接在两个进程之间”一个往一头倒水另一个从另一头接水适合有亲缘关系的进程命名管道则是这根“水管”有了名字任意两个进程都能找到它来通信。4.2 实验三信号通信——按个门铃告诉父进程“我准备好了”信号通信的核心思路子进程用kill(getppid(), SIGALRM)给父进程发送一个信号父进程提前用signal(SIGALRM, ouch)注册好处理函数信号一到处理函数里的变量就改变父进程主循环据此响应。#include unistd.h #include sys/types.h #include stdlib.h #include stdio.h #include signal.h static int alarm_fired 0; // 信号是否到达的标记 void ouch(int sig) // 信号处理函数 { alarm_fired 1; // 收到信号就把标记置1 } int main() { pid_t pid; pid fork(); if (pid 0) { perror(fork failed\n); exit(1); } else if (pid 0) { // 子进程向父进程发送 SIGALRM 信号 kill(getppid(), SIGALRM); exit(0); } else { // 父进程注册信号处理函数 signal(SIGALRM, ouch); while (!alarm_fired) { printf(no signal\n); } if (alarm_fired) printf(\nI got a signal %d\n, SIGALRM); } return 0; }这个实验你不需要理解得很深先抓三个要素谁发信号子进程用kill、发什么信号SIGALRM、谁处理父进程用signal注册函数ouch。这种“注册—发送—回调”的模式后面学很多系统编程都会遇到。运行后你会看到父进程疯狂打印 “no signal”直到某一刻突然变成 “I got a signal”然后程序结束。这就是“子进程的通知到达了父进程”的直观证据。4.3 实验四匿名管道——一根只能父子之间用的“数据水管”管道pipe是最经典的进程通信方式思路就是一个进程往管道里写另一个进程从管道里读数据像水流一样单向流动。#include stdio.h #include unistd.h #include stdlib.h #include string.h #include sys/types.h #define MAX_LINE 80 int main() { int fd[2]; // fd[0] 读端fd[1] 写端 pid_t child_pid; if (pipe(fd) 0) { // 创建管道 printf(管道创建成功\n); child_pid fork(); // 创建子进程 if (child_pid -1) { printf(子进程创建失败\n); } else if (child_pid 0) { // 子进程负责读数据 printf(子进程创建成功\n); close(fd[1]); // 子进程用不到写端先关掉 sleep(3); // 等父进程先写入 char readbuf[MAX_LINE]; read(fd[0], readbuf, sizeof(readbuf)); printf(子进程读取数据成功\n); printf(data%s\n, readbuf); close(fd[0]); exit(0); } else { // 父进程负责写数据 close(fd[0]); // 父进程用不到读端先关掉 char str[] hello the world; write(fd[1], str, strlen(str)); printf(父进程写入数据成功\n); close(fd[1]); // 写完关闭写端 printf(父进程关闭写端成功\n); return 0; } } else { printf(管道创建失败\n); } }初学者最常见的困惑是管道明明是父子进程共享的为什么要关闭一个端这是这张代码里最容易踩的坑。解释一下管道有两个端口读端fd[0]和写端fd[1]子进程只负责读所以把写端fd[1]关掉父进程只负责写所以把读端fd[0]关掉。如果不关双方都握着两个端口语义就乱了。这段代码里sleep(3)同样是给父进程写入留时间。写完数据父进程关闭写端子进程的read会发现“写入端已关闭”于是返回已读到的数据。运行结果管道创建成功 子进程创建成功 父进程写入数据成功 父进程关闭写端成功 子进程读取数据成功 datahello the world如果你把子进程的sleep(3)去掉子进程可能先于父进程执行read而管道里还没有数据read会阻塞等待数据到了才会继续。这个“读写阻塞”机制本身就是管道通信的精髓之一。4.4 实验五命名管道——给水管起个名字不认识的进程也能用匿名管道有个限制只能在有亲缘关系父子的进程间用因为子进程是 fork 出来的天然继承了这个管道。那如果两个毫无关系的进程想通信用什么答案是命名管道FIFO它在文件系统里有一个真实路径比如/tmp/myfifo任何进程只要知道这个路径打开它就能读写。#include stdio.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include string.h int main() { int ret mkfifo(./fifo, 0666); // 创建命名管道文件 if (ret 0) { printf(管道创建失败\n); exit(0); } else { printf(命名管道创建成功\n); } pid_t fpid fork(); if (fpid 0) { printf(创建子进程失败\n); } else if (fpid 0) { // 父进程以只读方式打开命名管道 int fd open(./fifo, O_RDONLY); if (fd 0) { printf(读数据出错\n); exit(0); } char buf[BUFSIZ] {0}; int ret read(fd, buf, sizeof(buf)); if (ret 0) { printf(父进程read buffer后 %s\n, buf); } close(fd); } else { // 子进程以只写方式打开命名管道写入数据 int fd open(./fifo, O_WRONLY); if (fd 0) { printf(写数据出错\n); exit(0); } char buf[100] HELLO,WORLD; sleep(1); printf(子进程写入管道\n); int ret1 write(fd, buf, strlen(buf)); close(fd); } return 0; }注意mkfifo(./fifo, 0666)的第二个参数 0666 是权限位意思是“所有人都能读写这个管道文件”。实验里父进程用O_RDONLY只读打开子进程用O_WRONLY只写打开各取所需。这里有个重要机制父进程open只读端的时候会一直阻塞到有进程以写方式打开同一管道——也就是说读写两端的 open 是“互相等待”的。跟实验四对比你会发现代码结构非常像区别只在于“管道有没有名字”匿名管道用一个fd[2]数组在内存里创建命名管道在文件系统里创建了一个可见的管道文件。理解了这层你就把两种管道的关系彻底打通了。4.5 三个通信方式怎么选一个表格说清楚通信方式传什么方向适用场景关键函数信号简单的通知单向通知事件发生kill()、signal()匿名管道字节流单向父子进程间传数据pipe()、read()、write()命名管道字节流单向任意进程间传数据mkfifo()、open()、read()、write()初学阶段记住这个规律就行要传复杂数据优先考虑管道只是通知一声用信号更轻量。等你后面学到消息队列、共享内存会发现它们其实是管道思想在更大规模上的延伸。5. 进阶必看信号量怎么解决“多个进程抢资源”的经典难题5.1 从生活中的排队说起信号量的本质是什么想象只有一个厕所五个人都要用。如果没人管理大家一拥而上场面会怎样信号量就是这个“厕所管理员”它维护一个数字进厕所的人让数字减 1出厕所的人让数字加 1数字为 0 时后来的人只能在门口排队等着。这个数字就是信号量专业叫法是“P 操作减 1V 操作加 1”。P 操作Proberen荷兰语“尝试”在做一件事如果信号量大于 0就减 1 进入临界区如果等于 0就阻塞等待。V 操作Verhogen“增加”则是加 1并唤醒等待的进程。操作系统课程里的“生产者—消费者问题”就是这个机制的经典考题。5.2 实验六完整代码解剖三个信号量 两个消费者实验六比前面复杂不少它的目标场景是生产者生产产品编号 1 到 100消费者取走产品并累加求和缓冲区只有 5 个位置生产者不能生产太快消费者不能取走空位置。代码里信号量有三个作用emptyid表示空位数量fullid表示已生产产品数量mutxid是互斥锁保证同一时刻只有一个进程在操作共享缓冲区。#include sys/mman.h #include sys/types.h #include sys/sem.h #include fcntl.h #include unistd.h #include stdio.h #include errno.h #include time.h #include stdlib.h #define MAXSEM 5 union semun { int val; struct semid_ds *buf; unsigned short *array; }; int fullid, emptyid, mutxid; int main() { struct sembuf P, V; union semun arg; int *array, *sum, *get, *set; // 用mmap创建共享内存让多个进程能访问同一片数据 array (int *)mmap(NULL, sizeof(int) * MAXSEM, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); sum (int *)mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); set (int *)mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); get (int *)mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); // 创建三个信号量 fullid semget(IPC_PRIVATE, 1, IPC_CREAT | 0666); emptyid semget(IPC_PRIVATE, 1, IPC_CREAT | 0666); mutxid semget(IPC_PRIVATE, 1, IPC_CREAT | 0666); // 初始化信号量full0还没有产品empty55个空位mutex1可用 arg.val 0; semctl(fullid, 0, SETVAL, arg); arg.val MAXSEM; semctl(emptyid, 0, SETVAL, arg); arg.val 1; semctl(mutxid, 0, SETVAL, arg); // 定义P操作减1和V操作加1 V.sem_num 0; V.sem_op 1; V.sem_flg SEM_UNDO; P.sem_num 0; P.sem_op -1; P.sem_flg SEM_UNDO; // 创建生产者进程 pid_t fpid1 fork(); if (fpid1 0) { int i 0; while (i 100) { semop(emptyid, P, 1); // P(empty)占一个空位 semop(mutxid, P, 1); // P(mutex)进入临界区 array[(*set) % MAXSEM] i 1; // 往缓冲区放产品 (*set); printf(produce %d\n, i 1); semop(mutxid, V, 1); // V(mutex)离开临界区 semop(fullid, V, 1); // V(full)产品数加1 i; } sleep(1); printf(producer is over\n); exit(0); } else { // 创建两个消费者进程 pid_t fpid2 fork(); if (fpid2 0) { while (1) { semop(fullid, P, 1); // P(full)是否有产品可取 semop(mutxid, P, 1); // P(mutex)进入临界区 if (*get 100) break; *sum array[(*get) % MAXSEM]; printf(the consumer A get number %d\n, array[(*get) % MAXSEM]); (*get); if (*get 100) { printf(the sum is %d\n, *sum); break; } semop(mutxid, V, 1); // V(mutex)离开临界区 semop(emptyid, V, 1); // V(empty)空位加1 sleep(0.5); } printf(consumer A is over\n); exit(0); } else { while (1) { semop(fullid, P, 1); semop(mutxid, P, 1); if (*get 100) break; *sum array[(*get) % MAXSEM]; printf(the consumer B get number %d\n, array[(*get) % MAXSEM]); (*get); if (*get 100) { printf(the sum is %d\n, *sum); break; } semop(mutxid, V, 1); semop(emptyid, V, 1); sleep(0.5); } printf(consumer B is over\n); exit(0); } } return 0; }这段代码的难点不是语法而是理解三个信号量各管什么。缓冲区一共 5 格生产者每次生产前先P(empty)——如果有空位就减 1没有就等着生产完再V(full)——产品数加 1。消费者恰好反过来先P(full)确保有产品可拿取走后V(empty)释放空位。mutxid则是“厕所门锁”不管生产还是消费进入缓冲区操作前都要先P(mutex)操作完再V(mutex)保证同一时刻只有一个人动共享数据。5.3 为什么实验报告作者说“刚开始翻车了”一个 P/V 顺序错误引发的血案实验报告最后写了一句值得注意的复盘消费者取到了生产者还没生产的产品。这在并发编程里是经典 bug——两个消费者抢跑在生产者把产品放进缓冲区之前就执行了取操作。根源往往是某个 P/V 操作顺序写反了比如生产者先V(full)再生产消费者一看 full 大于 0 就来取结果取了个空。这类问题最大的杀伤力在于“不是每次都会出现”可能跑十次九次正常偶尔一次出错最难排查。如果你在实验里遇到这种“间歇性怪病”最有用的排查手段是在每个 P/V 操作前后加printf打印信号量当前值然后让程序多跑几遍观察错乱时那个值是什么。我当时的血泪经验是把打印信息关掉之前先在代码里把所有semop调用按“哪个信号量、P 还是 V”列成一张清单一眼就能看出顺序错在哪。6. 新手避坑指南六个实验里的高频翻车点6.1 编译就失败忘记链接线程库现象gcc thread.c -o thread编译时报错提示pthread_create未定义引用。原因Linux 的线程库默认不链接需要手动加-lpthread参数。解决编译命令改成gcc thread.c -o thread -lpthread。信号量相关实验如果用了sem_系列函数也建议加上-lrt或-lpthread。6.2 运行就崩溃报段错误文件不存在现象实验一运行后输出“未找到文件”。原因程序里用相对路径fopen(test1file.txt, r)打开文件但当前目录下根本没有这个文件。解决先创建文件再运行程序命令是echo hello test1file.txt。这个坑特别容易在换目录环境时出现写实验前先ls确认文件在。6.3 程序卡住不动管道读写两端不匹配现象实验四或实验五运行后终端一直停住既不报错也不退出。原因管道通信中读端在等数据但写端没写或者写端在等数据读走但读端没读。典型情况是父子进程都忘了关闭不该用的那一端。解决先检查代码里是否关闭了多余端再看写数据、读数据的顺序是否配对。排查时分别在read前、write后加printf打点就能定位卡在哪一行的哪个等待。6.4 输出乱序父进程不等人现象实验一去掉waitpid后父进程的输出跑到子进程前面去了。原因父进程没有等待子进程两条流水线各跑各的输出顺序难以预测。解决确认父进程分支里有waitpid(0, NULL, 0)并且printf的内容能看到“等待中”的标记。如果只想让父子进程短暂配合也可以用实验四里的sleep兜底但正式场景必须用同步函数。6.5 消费者取到“空中产品”P/V 顺序写反现象实验六运行过程中消费者输出的产品编号出现重复或跳号甚至出现不存在的编号。原因某个信号的 P/V 顺序写反导致消费者在生产者未完成放入前就进入临界区取数据。解决按这个模板自查——生产者的循环里应该是“P(empty) → P(mutex) → 放入 → V(mutex) → V(full)”消费者是“P(full) → P(mutex) → 取出 → V(mutex) → V(empty)”。任何一步不按这个顺序逻辑就出问题。6.6 命名管道创建失败上一次的管道文件还在现象实验五第二次运行时程序直接退出提示“管道创建失败”。原因上一次运行创建的./fifo文件还留在磁盘上mkfifo发现同名文件已存在就会失败。解决重新运行前先执行rm -f ./fifo或者代码里判断mkfifo失败时如果是文件已存在的错误EEXIST就继续往下走而不是直接退出。7. 下一步怎么学从这份报告到真正会写并发程序六个实验做下来你已经亲手碰过了进程、线程、信号、管道、命名管道、信号量和共享内存这其实是操作系统“并发与通信”模块的核心骨架。接下来建议按三条线继续深入顺序可以这样排。第一条线是补齐背后的理论。做个不恰当的比喻会写代码相当于会开车看懂调度器、虚拟内存这些相当于懂发动机原理。暂时不用啃大部头先把教材里“进程状态转换”“临界区与锁”“死锁的四个条件”这三节对着实验反复读几遍你会发现实验里亲手跑过的现象全都在课本里有对应原理。特别是信号量实验里那个“P/V 顺序写反导致取到空中产品”的 bug对应到课本上就是一个典型的同步错误。第二条线是动手改造实验。给实验四的管道加上双向通信让子进程也给父进程回一句话把实验六的单缓冲区改成多缓冲区把生产者从 1 个改成 3 个或者给实验二的线程加一个pthread_join把 sleep 的笨办法换掉。我在学完这些实验后做的一件事是写一个模拟“餐厅点餐”的小程序一个线程接收订单、两个消费者线程处理订单用信号量控制订单队列的长度——本质上就是实验六的换皮但做完后你会发现自己真的能设计并发程序了。第三条线是接触现代工具链。实验里用的fork、pipe、semget都是 Unix 的经典接口它们在真实系统里依然大量存在但如今写并发多线程程序时更多人会用pthread的互斥锁、条件变量、读写锁或者更高层封装的线程池框架。建议先回头把pthread系列的pthread_mutex_lock与pthread_cond_wait练熟这两个函数就是信号量思想的现代替代品理解了 P/V你学它们会快得多。从那以后我每次做完这类实验都强制自己走一遍“重跑三遍 看顺序 检查资源释放”的流程确认没有资源泄漏和随机 bug 才收工。希望这份指南能帮你把实验从“照着抄”变成“真懂了”的起点。如果一次跑不通不要急着怀疑自己——并发程序的 bug 本来就是玄学重灾区你在这上面踩的每一次坑都会变成后面面试和项目里的底气。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑