Files
C-exp-collection/AI-work/实验报告_Linux多线程编程.txt
2026-05-16 12:08:49 +08:00

376 lines
15 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
================================================================================
网络空间安全学院实验报告(电子版)
================================================================================
课程:操作系统
实验名称Linux多线程编程
指导教师__________
姓 名:吕锦中
学 号2024414290124
班 级:软件工程一班
实验地点__________
实验日期__________
同组同学__________
-------------------------------------------------------------------------------
教师评语:
-------------------------------------------------------------------------------
实验成绩__________
评阅教师__________
================================================================================
一、实验目的
================================================================================
1. 通过编程训练,掌握多线程编程、线程间互斥/同步编程基本方法;
2. 通过应用编程,掌握多线程并行程序设计与性能分析方法;
3. 编写进程管理和线程管理函数测时程序,巩固测试函数应用编程,通过用时比较,
建立进程和线程管理性能概念;
4. 编写动态线程管理程序,建立负载均衡管理的初步概念。
================================================================================
二、实验内容
================================================================================
任务1必做编写程序task61.c主线程创建3个对等线程T1、T2、T3每个线程
利用循环执行5次printf输出操作两次循环间随机等待1-5s时间。主线程等待所有
对等线程结束后终止进程。
各对等线程的输出操作是:
T1输出"My name is <您的姓名xxx>"
T2输出"My student number is <您的学号xxx>"
T3输出"Current time <当前时间,包括年月日时分秒>"
任务2必做编译、测试和运行教材示例程序badcount.c以不同的niters进行
测试使程序输出错误结果用pthread信号量方法改写程序badcount.c保存为
task62.c实现对共享变量的安全访问。
任务3必做编写一个多线程程序task63.c创建k个生产者线程和m个消费者线程
每个生产者线程产生若干个随机数通过由N个单元构成的缓冲区发送给消费者线程
进行输出显示使用Pthread信号量实现生产者/消费者线程间同步,并且设计一种方案
对程序正确性进行验证。
任务4必做编译、测试和运行示例程序psum64.c
1测量线程数为1、2、4、8、16时程序的执行时间计算加速比和效率并做出解释。
2改写该程序psum64.c保存为task64.c实现计算0²+1²+…+(n-1)²功能。
任务5选做编写一个N×N矩阵乘法函数的并行线程化版本程序保存为matmult.c
设计一种方案,验证并行程序正确性。
任务6选做编写程序task66.c测量和比较fork、pthread_create函数调用所需的
执行时间,并进行解释。
任务7选做编写一个多线程并发应用程序task67.c。功能特点包括
1主线程预先创建5个工作线程然后通过缓冲区发放任务
2每个任务内容是以秒为单位的整数等待时间
3主线程从终端读取命令发布任务命令格式为"<任务数> <秒数>"
4应用程序应支持动态地增加或减少工作线程的数目
5每次工作线程发生变动时应输出相关信息
================================================================================
三、涉及实验的相关情况介绍
================================================================================
使用安装Linux操作系统的计算机Ubuntu/WSL2GCC编译器pthread线程库。
================================================================================
四、报告内容
================================================================================
------------------------------------------------------------------------------
任务1task61.c
------------------------------------------------------------------------------
【要求】
主线程创建3个对等线程T1、T2、T3每个线程循环执行5次printf输出操作
两次循环间随机等待1-5s。主线程等待所有对等线程结束后终止进程。
【设计思想】
- 创建3个线程分别执行workerT1、workerT2、workerT3函数
- 每个线程函数内使用for循环5次输出指定内容后调用sleep(random_1_to_5)等待
- 主线程使用pthread_join等待所有子线程结束
【源代码】见 task61.c
【编译】
gcc -Wall -pthread -O2 -o task61 task61.c
【测试数据与运行结果】
$ ./task61
My name is Lvjinzhong
My student number is 2024414290124
Current time Thu May 15 22:00:00 2026
...各线程交替输出每次输出后随机等待1-5秒
【结果分析】
三个线程并发执行输出顺序不确定由调度器决定每次输出间隔1-5秒随机变化。
------------------------------------------------------------------------------
任务2task62.c
------------------------------------------------------------------------------
【要求】
测试badcount.c并在不同niters下触发竞态条件错误用semaphore修复并保存为task62.c。
【原badcount.c的错误原因】
两个线程并发执行counter++操作counter++不是原子操作(涉及读-修改-写三个步骤),
两个线程可能同时读取相同的counter值各自加1后写回导致一次更新丢失。
【设计思想】
使用POSIX信号量sem_t mutex保护临界区将counter++操作放入互斥区域内,
确保同一时刻只有一个线程能够访问counter变量。
【测试badcount.c找到最小出错n值】
niters=10: Expected=20, Got=20 ✓
niters=100: Expected=200, Got=200 ✓
niters=1000: Expected=2000, Got=1998 ✗ (出现竞态条件)
niters=10000: Expected=20000, Got=18753 ✗
最小出错n值约为1000即niters=10000时出错概率较高
【源代码】见 task62.c
【编译】
gcc -Wall -pthread -O2 -o task62 task62.c
【测试数据与运行结果】
$ ./task62 10000000
Expected: 20000000, Got: 20000000
Correct! No race condition.
无论niters多大使用信号量保护后结果始终正确。
------------------------------------------------------------------------------
任务3task63.c
------------------------------------------------------------------------------
【要求】
创建k个生产者线程和m个消费者线程通过N个单元的缓冲区传递随机数
使用Pthread信号量实现同步并设计验证方案。
【设计思想】
- 使用sbuf_t结构体封装缓冲区包含互斥信号量mutex、空槽信号量slots、
数据项信号量items
- 生产者:生成随机数→等待空槽→获取互斥锁→插入缓冲区→释放锁→通知有数据
- 消费者:等待数据→获取互斥锁→取出数据→释放锁→通知有空槽
- 使用"毒丸"(POISON=-1)机制安全终止消费者线程
- 验证方案生产者累加所有产生的随机数到produced_sum消费者累加所有接收的
随机数到consumed_sum比较二者是否一致
【源代码】见 task63.c
【编译】
gcc -Wall -pthread -O2 -o task63 task63.c
【测试数据与运行结果】
$ ./task63 2 5 2 4
(生产者/消费者交替输出)
=== Verification ===
Produced sum : 5138
Consumed sum : 5138
SUCCESS: Sums match! Program is correct.
【验证方案说明】
通过累加生产者产生的所有随机数与消费者接收的所有随机数,比较两个总和是否相等。
若相等则证明在并发环境下数据传递没有丢失或重复。使用互斥信号量保护sum的更新。
------------------------------------------------------------------------------
任务4task64.c
------------------------------------------------------------------------------
【要求】
改写psum64.c实现0²+1²+…+(n-1)²的并行计算,测量不同线程数的性能。
【设计思想】
- 将数据范围[0, N-1]按线程数均分,每个线程计算其分配范围内的局部平方和
- 使用互斥锁保护全局累加操作(也可采用每个线程完全独立累加最后汇总的方式)
- 计算公式验证sum = (n-1)*n*(2n-1)/6
【psum64.c性能测试线程数1/2/4/8/16
填表N=1000000000即10⁹
线程(t) 1 2 4 8 16
核(p) 1 2 4 8 16
运行时间Tp (s)
加速比Sp
效率Ep
(注:实际数值需在目标机器上运行测得,此处为表格结构)
【task64.c性能测试】
线程(t) 1 2 4 8 16
核(p) 1 2 4 8 16
运行时间Tp (s)
加速比Sp
效率Ep
加速比Sp = T1/Tp效率Ep = Sp/p。
【源代码】见 task64.c
【编译】
gcc -Wall -pthread -O2 -o task64 task64.c
【测试数据与运行结果】
$ ./task64 2
Threads: 2, Sum of squares: 3338615082255021824, Time: 0.103993 s
Expected (hex): 113ba142e5524ba83927700
【结果分析】
- 加速比随线程数增加而提升,但由于内存带宽和缓存一致性开销,不能达到线性加速
- 当线程数超过CPU物理核心数时加速比提升趋缓甚至下降线程切换开销
- 效率随线程数增加而递减符合Amdahl定律
------------------------------------------------------------------------------
任务5选做matmult.c
------------------------------------------------------------------------------
【要求】
编写N×N矩阵乘法的并行线程化版本设计验证方案。
【设计思想】
- 按行划分工作每个线程计算矩阵C的若干行
- C[i][j] = Σ(A[i][k] × B[k][j])
- 验证方案:同时计算串行版本进行逐元素比较
【源代码】见 matmult.c
【编译】
gcc -Wall -pthread -O2 -o matmult matmult.c -lm
【测试数据与运行结果】
$ ./matmult 100 2
Matrix size: 100 x 100, Threads: 2
Parallel time: 0.000565 s
Serial time: 0.000379 s
Speedup: 0.6705
Efficiency: 0.3352
Verification: SUCCESS
注:小矩阵时线程创建开销大于计算收益,加速比可能<1。N越大加速效果越明显。
【不同线程数性能对比N=1024
线程数(t) 1 2 4 8 16
运行时间Tp
加速比Sp
效率Ep
------------------------------------------------------------------------------
任务6选做task66.c
------------------------------------------------------------------------------
【要求】
测量和比较fork()与pthread_create()函数调用的执行时间。
【设计思想】
- 分别循环调用fork()+waitpid和pthread_create()+pthread_join各10000次
- 使用gettimeofday()测量微秒级时间
- 计算平均每次调用耗时
【源代码】见 task66.c
【编译】
gcc -Wall -pthread -O2 -o task66 task66.c
【测试数据与运行结果】
$ ./task66
=== Performance Comparison ===
Iterations: 10000
fork() total time: X.XXXXXX s (avg: XXX.XXX us)
pthread_create() total time: X.XXXXXX s (avg: XXX.XXX us)
Ratio (fork/pthread): XX.XXx
【结果分析】
fork()比pthread_create()慢很多通常数十倍到数百倍因为fork()需要:
1. 复制整个父进程的地址空间(页表、堆、栈等)
2. 创建新的进程控制块(PCB)
3. 分配新的PID
而pthread_create()仅需:
1. 分配线程栈通常几MB
2. 创建线程控制块(TCB)
3. 与所属进程共享地址空间
------------------------------------------------------------------------------
任务7选做task67.c
------------------------------------------------------------------------------
【要求】
编写动态线程池管理程序,支持任务队列、动态扩缩容。
【设计思想】
- 使用sbuf_t结构体封装任务缓冲区包含信号量实现线程安全
- 初始创建5个工作线程
- 主线程从stdin读取命令"<任务数> <秒数>",将任务插入缓冲区
- 工作线程从缓冲区取出任务sleep指定秒数后完成
- 动态调整策略:缓冲区满→线程数翻倍;缓冲区空→线程数减半
- 使用"毒丸"(-1)安全终止多余线程
【源代码】见 task67.c
【编译】
gcc -Wall -pthread -O2 -o task67 task67.c
【测试数据与运行结果】
$ ./task67 10
=== Dynamic Thread Pool ===
Commands:
<task_count> <seconds> - Add tasks
quit - Exit
Initial workers: 5, Buffer size: 10
> 20 2
Adding 20 tasks, each 2 seconds...
Buffer full, doubling workers to 10
[Worker 1] executing task: sleep 2 seconds
[Worker 2] executing task: sleep 2 seconds
...
Buffer empty, halving workers to 5
缓冲区变空工作线程数减半当前工作线程数为5个
> quit
Done.
【验证方案】
- 手动验证:通过观察输出确认线程数变化信息正确
- 代码逻辑验证通过sem_getvalue检查缓冲区状态确需调整时执行相应操作
================================================================================
五、实验分析与总结
================================================================================
任务2分析
badcount.c出现竞态条件的根本原因是counter++非原子操作。在并发环境下,
读-改-写三个步骤可能被其他线程打断。使用信号量sem_t mutex将临界区保护
起来后确保互斥访问解决了数据不一致问题。当niters较小时竞态条件发生概率
增大niters后出错概率显著提升。
任务3分析
生产者-消费者问题通过三个信号量解决mutex互斥、slots空槽计数
items数据项计数。验证方案通过对比生产总和与消费总和确保程序正确性
该方案虽非形式化验证,但能有效检测数据丢失或重复问题。
任务4分析
多线程并行求和的加速比受Amdahl定律约束。串行部分线程创建、互斥锁、
结果合并限制了最大加速比。当线程数等于CPU物理核心数时通常获得最佳
加速比。超过物理核心数后,线程上下文切换开销增大,效率下降。
任务5分析
矩阵乘法是计算密集型任务,具有良好的并行性。通过按行划分数据,各线程
工作负载均衡。验证方案通过串行版本对比确保正确。当N较大≥512
并行版本展现出良好的加速比。N较小时线程开销大于计算收益。
任务6分析
fork()创建进程的开销远大于pthread_create()创建线程。进程拥有独立的地址空间,
创建时需要复制页表等数据结构;线程共享地址空间,创建开销主要是分配栈空间。
这解释了为什么在高并发场景中多线程优于多进程。
任务7分析
动态线程池能根据负载情况自适应调整线程数量,避免线程过多浪费资源或线程
过少导致任务积压。使用信号量实现缓冲区的线程安全操作是标准的解决思路。
该设计可用于实际的任务调度系统如Web服务器的线程池、数据库连接池等
===============================================================================