实验报告
实验内容
线程(1):
编译运行课件 Lecture13 例程代码:
Algorithms 13-1 ~ 13-8.
实验环境
Ubuntu 20.04.2.0(64位)
实验过程
一. alg.13-1-pthread-create-1-1.c
(一)对代码中不熟悉的内容进行解析:
pthread_attr_init()
(参考资料)
头文件:#include<pthread.h>
函数原型:int pthread_attr_init(pthread_attr_t *attr);
函数功能: 初始化一个线程对象的属性。
参数:
attr:指向一个线程属性结构的指针
返回值: 返回0,表示函数初始化对象成功。失败时返回一个错误代码。
pthread_create()
(参考资料)
头文件:#include<pthread.h>
函数原型:int pthread_create(pthread_t *tidp,const pthread_attr_t *attr, void *(*start_rtn)(void*),void *arg);
函数功能: 创建线程
参数:
tidp: 指向线程标识符的指针
attr: 用来设置线程属性
(start_rtn)(void): 线程运行函数的起始地址
arg: 运行函数的参数
返回值: 若线程创建成功,则返回0。若线程创建失败,则返回出错编号。
pthread_join()
(参考资料)
函数原型:int pthread_join(pthread_t thread, void **retval);
函数功能: 用来等待一个线程的结束,线程间同步的操作
参数:
thread: 线程标识符
retval: 用户定义的指针,用来存储被等待线程的返回值
返回值: 0代表成功。失败,返回的则是错误号
pthread_exit()
(参考资料)
函数原型:void pthread_exit(void* retval);
参数:
retval: 用户定义的指针,用来存储被等待线程的返回值
(二)运行
-
程序先通过
pthread_create(&ptid, &attr, &runner, argv[1])
创建了一个线程。该线程执行函数static void *runner(void *param)
,传入参数为argv[1]
。 -
主线程
main()
继续执行直到函数pthread_join(ptid, (void **)&retptr)
,挂起,等待子线程ptid
结束。 -
子线程执行,通过
pthread_exit((void *)retptr)
结束线程,返回指定参数。 -
主线程继续执行直到结束,在子线程中申请的内存(返回参数的内存)要在主线程中释放。
二. alg.13-1-pthread-create-1-2.c
(一)运行
该程序的运行与程序一一致,区别是将计算的结果(全局变量的指针)作为子线程的返回值。
三. alg.13-1-pthread-create-1-3.c
(一)运行
该程序的运行与程序一一致,区别是不使用全局变量,而是通过主线程中的指针来传递计算结果。
四. alg.13-1-pthread-create-2.c
(一)运行
该程序的运行与程序一一致,区别是返回的指针类型为char。
五. alg.13-1-pthread-create-3-1.c
(一)运行
主线程根据用户输入的参数创建相应数量的子线程(运行函数 static void *ftn(void *arg)
),由于每创建一个线程后会 sleep 1秒,所以线程在创建时标号不会乱且不会重复。
主线程等待所有子线程结束后再继续执行。
六. alg.13-1-pthread-create-3.c
(一)运行
该程序的运行与程序四一致,区别是创建线程时不 sleep,导致线程在创建时标号发生混乱和重复。
七. alg.13-1-pthread-create-4.c
(一)运行
该程序的运行与程序四一致,区别是创建线程时不 sleep,但先给每个线程分配好了标号,因此线程在创建时标号不会重复。
八. alg.13-1-pthread-create.c
(一)运行
该程序的运行与程序一一致,区别是子线程结束时不返回任何参数。
九. alg.13-2-pthread-shm.c
(一)运行
通过子线程来改变共享内存的内容。子线程1改变msg1的内容,子线程2改变msg2的内容。
十. alg.13-3-pthread-stack.c
(一)对代码中不熟悉的内容进行解析:
pthread_attr_setstack()
(参考资料)
头文件:#include<pthread.h>
函数原型:int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
函数功能: 设置栈的属性
参数:
attr:指向一个线程属性结构的指针
stackaddr: 要写入 attr 指向的 stack address
stacksize: 要写入 attr 指向的 stack size
返回值: 返回0,表示函数成功。失败时返回一个错误代码。
(二)运行
先设置线程的栈属性,然后测试创建的线程的栈的大小。
十一. alg.13-4-openmp-demo.c
(一)对代码中不熟悉的内容进行解析:
#pragma omp parallel
头文件:#include <omp.h>
功能: 其后大括号中的语句将被多个线程并行执行,线程个数由系统预设或自行设置。
(二)运行
1由2个线程(系统自行预设)并行执行;2由2个线程(用户设置)并行执行;3由4个线程(用户设置)并行执行;4由6个线程(用户设置)并行执行。
十二. alg.13-5-openmp-matrixadd.c
(一)运行
比较不同线程下矩阵加法的运算时间。
可以看到当矩阵的规模较小时,单线程所用时间更短,此时多线程的创建开销不划算。
当矩阵规模较大时,2个线程的计算时间更短,此时创建2个子线程的开销是值得的。
而4个线程的计算时间总是相对最长的,猜测是矩阵的规模还不够大,无法弥补创建4个子线程的开销。
十三. alg.13-6-fork-pthread-demo1.c
(一)运行
先创建一个线程在 fork
,子进程只会从 fork
处继续往下执行,因此此时是三个进程。子进程结束后就只剩下两个进程。
由于父进程没有等待子线程结束,因此父进程结束时子线程仍在运行(循环),需要手动终止它。
十四. alg.13-7-fork-pthread-demo2.c
(一)运行
先创建一个子线程,然后在子线程中 fork,由于父线程直接 return 1
结束父线程及其创建的子线程,因此子线程 fork 出来的线程一直处于运行状态。需要通过命令 pkill -f a.out
才能结束其运行。
将 return 1
去掉再运行。
父线程创建的子线程和子线程 fork 出来的线程同时在运行,此时父线程挂起,等待子线程结束。
十五. alg.13-8-sigaction-demo.c
(一)对代码中不熟悉的内容进行解析:
sigemptyset()
(参考资料)
头文件:#include<signal.h>
函数原型:int sigemptyset(sigset_t *set);
函数功能: 用来将参数set信号集初始化并清空。
返回值: 执行成功则返回0,如果有错误则返回-1
sigaddset()
(参考资料)
头文件:#include<signal.h>
函数原型:int sigaddset(sigset_t *set,int signum);
函数功能: 用来将参数signum 代表的信号加入至参数set 信号集里。
返回值: 执行成功则返回0,如果有错误则返回-1
sigaction()
(参考资料)
头文件:#include<signal.h>
函数原型:int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);
函数功能: 检查或修改与指定信号相关联的处理动作(可同时两种操作)
参数:
signum:指出要捕获的信号类型
act:指定新的信号处理方式
oldact:输出先前信号的处理方式(如果不为NULL的话)
返回值: 执行成功则返回0,如果有错误则返回-1
(二)运行
等待抓取到按键 Ctrl + c
抓取到 Ctrl + c 后
睡眠10秒后
等待抓取到按键 Ctrl + \
抓取到 Ctrl + \ 后,成功退出。