信息安全系统第十三周学习总结 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),给所有的锁分配一个全序,每个线程按照这个顺序来请求锁,并且按照逆序来释放,这个程序就是无死锁的。

解决死锁的方法

不让死锁发生:
  • 静态策略:设计合适的资源分配算法,不让死锁发生---死锁预防
  • 动态策略:进程在申请资源时,系统审查是否会产生死锁,若会产生死锁则不分配---死锁避免
让死锁发生:

进程申请资源时不进行限制,系统定期或者不定期检测是否有死锁发生,当检测到时解决死锁----死锁检测与解除

posted @ 2015-12-06 23:16  偷影子的人  阅读(322)  评论(0编辑  收藏  举报