================================================================================
        网络空间安全学院实验报告（电子版）
================================================================================

课程：操作系统
实验名称：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/WSL2），GCC编译器，pthread线程库。

================================================================================
四、报告内容
================================================================================

------------------------------------------------------------------------------
任务1：task61.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秒随机变化。

------------------------------------------------------------------------------
任务2：task62.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多大，使用信号量保护后结果始终正确。

------------------------------------------------------------------------------
任务3：task63.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的更新。

------------------------------------------------------------------------------
任务4：task64.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服务器的线程池、数据库连接池等）。

===============================================================================
