26SWOS。本次实验基于 BC‑Linux 环境,使用 POSIX pthread 线程库实现并行归并排序,理解多线程并发编程、线程传参注意事项以及并行归并的实现思路。实验将大数据集切分为多个子集,利用多线程完成子集排序,之后多线程并行两两归并,体会并发编程带来效率提升的同时,理解多线程开发中需要规避的常见错误。在操作系统学习中,多线程并发编程是非常重要的一部分。线程共享进程的地址空间,可以并行执行任务,利用多核 CPU 提升程序运行效率,但同时也会带来线程调度、参数传递、资源竞争等一系列问题。本次实验在 BC‑Linux 容器环境下,基于 pthread 库实现多线程版本的归并排序,加深对 Linux 多线程的理解。本次实验程序主要分为两大阶段:子集并行排序阶段与循环并行归并阶段。程序接收两个命令行参数,分别代表数据总数量与每个子集的元素个数。程序首先生成一组随机整数,将整体数组切分成若干个子集。第一阶段创建多个子线程,每一个线程负责调用 qsort 对一个独立子集进行排序。因为各个子集操作的内存区域互不重叠,不存在共享资源竞争,因此这一阶段不需要互斥锁保护。这里有一个非常重要的知识点:向pthread_create传递线程参数时,禁止直接传递栈上的局部变量地址。子线程由操作系统调度,主线程创建线程之后,子线程并不会立刻运行。如果传入函数内部的局部变量,主线程函数执行结束后,栈上局部变量就会被销毁回收。当子线程获得 CPU 时间片想要读取参数时,这块内存已经失效,会读取脏数据,造成逻辑错误甚至段错误。正确的做法是使用malloc在堆内存分配参数结构体,堆内存不会随着函数退出自动释放。当所有子集都完成内部排序之后,进入第二阶段并行归并。程序循环将子集从左向右两两配对,每一对子集开启独立线程执行归并操作。归并使用手写 O (n) 复杂度的 merge 函数,不能再次调用 qsort。如果子集总数为奇数,落单的子集直接保留到下一轮,不参与本轮合并。每一轮全部归并线程执行结束后,打印本轮所有子集,继续下一轮归并,直到内存中只剩下一个完整有序的数组,排序结束。以输入参数./mymsort 25 5为例,初始会切分出 5 个子集。第一轮开启 2 个线程分别合并 1‑2、3‑4 子集,子集 5 落单;第二轮开启 1 个线程合并前两个合并后的大子集;第三轮将剩下两个子集合并,得到全局有序数组。编译该程序的时候,必须显式链接线程库,编译选项添加‑lpthread,同时引入数学库‑lm。运行后可以观察每一轮子集的输出,直观看到并行归并的全过程。通过本次实验,我掌握了 pthread 线程创建、等待线程结束pthread_join的使用方法,理解栈内存与堆内存的生命周期差异,同时理解并行归并排序的实现逻辑,认识到多线程虽然可以提高效率,但是参数传递、内存生命周期是开发中极易踩坑的关键点。
Linux 多线程并行归并排序实验探究