信息安全系统第十三周学习总结 20135218 姬梦馨
第十二章 并发编程
12.1 基于进程的并发编程
构造并发程序最简单的方法——用进程
常用函数如下:
- fork
- exec
- waitpid
【基于进程的并发服务器】
- 使用SIGCHLD处理程序来回收僵死子进程的资源。
- 父进程必须关闭他们各自的connfd拷贝(已连接的描述符),避免存储器泄露。
- 因为套接字的文件表表项中的引用计数,直到父子进程的connfd都关闭了,到客户端的连接才会终止。
【基于I/O多路复用的并发编程】
使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。
int select(int n,fd_set *fdset,NULL,NULL,NULL); 返回已经准备好的描述符的非0的个数,若出错则为-1。
【基于I/O多路复用的并发事件驱动服务器】
状态机:一组状态、输入事件、输出事件和转移。
自循环:同一输入和输出状态之间的转移。
I/O多路复用技术的优劣
相比基于进程的设计给了程序员更多的对进程行为的控制,运行在单一进程上下文中,每个逻辑流都能访问全部的地址空间,在流之间共享数据很容易。
编码复杂,随着并发粒度的减小,复杂性还会上升。粒度:每个逻辑流每个时间片执行的指令数量。
12.3 基于线程的并发编程
Posix线程
Posix线程是C语言中处理线程的一个标准接口,允许程序创建、杀死和回收线程,与对等线程安全的共享数据。
线程的代码和本地数据被封装在一个线程例程中,
创建线程
线程通过调用pthread_create来创建其他线程。
int pthread_create(pthread_t *tid,pthread_attr_t *attr,func *f,void *arg); 成功则返回0,出错则为非零
当函数返回时,参数tid包含新创建的线程的ID,新线程可以通过调用pthread_self函数来获得自己的线程ID。
pthread_t pthread_self(void);返回调用者的线程ID。
终止线程
当顶层的线程例程返回时,线程会隐式地终止。通过调用pthread_exit函数,线程会显式地终止
void pthread_exit(void *thread_return);
回收已终止的线程资源
线程通过调用pthread_join函数等待其他线程终止。
int pthread_join(pthread_t tid,void **thread_return); 成功则返回0,出错则为非零
分离线程
在任何一个时间点上,线程是可结合或可分离的。一个可结合的线程能够被其他线程收回其资源和杀死,在被回收之前,它的存储器资源是没有被释放的。分离的线程则相反,资源在其终止时自动释放。
int pthread_deacth(pthread_t tid); 成功则返回0,出错则为非零
初始化线程
pthread_once允许初始化与线程例程相关的状态。
pthread_once_t once_control=PTHREAD_ONCE_INIT; int pthread_once(pthread_once_t *once_control,void (*init_routine)(void)); 总是返回0
12.4 多线程程序中的共享变量
【线程存储器模型】
每个线程都有自己独立的线程上下文,包括一个唯一的整数线程ID,栈、栈指针、程序计数器、通用目的寄存器和条件码。
【将变量映射到存储器】
- 全局变量:定义在函数之外的变量
- 本地自动变量:定义在函数内部但是没有static属性的变量。
- 本地静态变量:定义在函数内部并有static属性的变量。
【共享变量】
当且仅当变量的一个实例被一个以上的线程引用时,就说变量是共享的。
12.5用信号量同步线程
【进度图】
将n个并发线程的执行模型化为一条n维笛卡尔空间中的轨迹线,将指令模型化为从一种状态到另一种状态的转换。
转换规则:
- 合法的转换是向右或者向上,即某一个线程中的一条指令完成
- 两条指令不能在同一时刻完成,即不允许出现对角线
- 程序不能反向运行,即不能出现向下或向左
【信号量】
- P(s):如果s是非零的,那么P将s减一,并且立即返回。如果s为零,那么就挂起这个线程,直到s变为非零。
- V(s):将s加一,如果有任何线程阻塞在P操作等待s变为非零,那么V操作会重启线程中的一个,然后该线程将s减一,完成他的P操作。
12.6 使用线程来提高并行性
并行程序的加速比通常定义为:
其中,p为处理器核的数量,T为在p个核上的运行时间。
12.7 其他并发问题
【线程安全】
四个不相交的线程不安全函数类以及应对措施:
- 不保护共享变量的函数——用P和V这样的同步操作保护共享变量
- 保持跨越多个调用的状态的函数——重写,不用任何static数据。
- 返回指向静态变量的指针的函数——重写;使用加锁-拷贝技术。
- 调用线程不安全函数的函数——参考之前三种
【竞争】
当一个程序的正确性依赖于一个线程要在另一个线程到达y点之前到达他的控制流x点时,就会发生竞争。
为消除竞争,我么可以动态地为每个整数ID分配一个独立的块,并且传递给线程例程一个指向这个块的指针。
【死锁】
死锁:一组线程被阻塞了,等待一个永远也不会为真的条件。
- 程序员使用P和V操作顺序不当,以至于两个信号量的禁止区域重叠。
- 重叠的禁止区域引起了一组称为死锁区域的状态。
- 死锁是一个相当难的问题,因为它是不可预测的。
互斥锁加锁顺序规则:如果对于程序中每对互斥锁(s,t),给所有的锁分配一个全序,每个线程按照这个顺序来请求锁,并且按照逆序来释放,这个程序就是无死锁的。
解决死锁的方法
不让死锁发生:
- 静态策略:设计合适的资源分配算法,不让死锁发生---死锁预防;
- 动态策略:进程在申请资源时,系统审查是否会产生死锁,若会产生死锁则不分配---死锁避免。
让死锁发生:
进程申请资源时不进行限制,系统定期或者不定期检测是否有死锁发生,当检测到时解决死锁----死锁检测与解除。