标签:
第十二章 并发编程
如果逻辑控制流在时间上重叠,那么它们就是并发的。
使用应用级并发的应用程序称为并发程序,三种基本的构造并发程序的方法:进程、I/O多路复用、线程
12.1基于进程的并发编程
第一步:服务器接受客户端的连接请求
第二步:服务器派生一个子程序为这个客户端服务
第三步:服务器接受另一个连接请求
第四步:服务器派生另一个子程序为新的客户端服务
12.1.1 基于进程的并发服务器
必须要包括一个SIGCHLD处理程序,来回收僵死子程序的资源
为避免存储器泄露,必须关闭各自的connfd拷贝
直到父子进程的connfd都关闭了,到客户端的的连接才会终止
12.1.2 关于进程的优劣
父子进程间的共享状态信息模型:共享文件表,但是不共享用户地址信息
有独立的地址空间:优点:一个进程不可能不小心覆盖另一个进程的虚拟存储器
缺点:共享状态信息变得困难,必须使用显式的IPC机制;比较慢
?
12.2 基于I/O多路复用的并发编程
服务器必须响应两个相互独立的I/O事件
基本思路:使用select函数,要求内核挂起进程
Select函数有两个输入:1.读集合的描述符集合 2.基数n
当且仅当一个从该描述符读取一个字节的请求不会阻塞时,描述符k就表示准备好可以读了
12.2.1 基于I/多路复用的并发事件驱动服务器
I/O多路复用可以用做并发事件驱动程序的基础
一个状态机就是一组状态、输入事件和转移
自循环是同一输入和输出状态之间的转移
活动客户端的集合维护在一个pool结构里
在通过调用init_pool初始化池之后,服务器进入一个无限循环
在循环的每次迭代中,服务器调用select函数来检测两种不同类型的输入事件:
Init_pool函数初始化客户端池
Add_client函数添加一个新的客户端到活动客户端池中
Check_clients函数回送来自每个准备好的已连接描述符的一个文本行
优点:1.比基于进程的设计给了程序员更多对程序行为的控制
2.每个逻辑流都能访问该进程的全部地址空间
缺点:1.编码复杂 2.不能充分利用多核处理器
?
12.3基于线程的并发编程
线程就是运行在进程上下文中的逻辑流
每个线程都有它自己的线程上下文,包括一个唯一的整数线程ID、栈、栈指针、程序计数器、通用目的寄存器和条件码
所有运行在一个进程里的线程共享该进程的整个虚拟地址空间
12.3.1线程执行模型
每个进程开始生命周期时都是单一线程,这个线程称为主线程
在某一时刻,线程创建一个对等线程,从这个时间点开始,两个线程就并发地运行
最后,因为主线程执行一个慢速系统调用,控制就会通过上下文切换传递到对等线程
对等线程执行一段时间,然后控制传递回主线程
12.3.2Posix线程
Posix线程是在C程序中处理线程的一个标准接口
Pthreads运行程序创建、杀死和回收线程,与对等线程安全地共享数据
12.3.3创建线程
Pthread_create函数
创建一个新的线程,并带着一个输入变量arg
在新线程的上下文中运行线程例程f
当其返回时,参数tid包含新创建线程的ID
12.3.4终止线程
方法:1.顶端线程例程返回(隐式)
2.调用pthread_exit函数(显式)
3.某个对等线程调用Unix的exit函数
4.另一个对等线程通过以当前线程ID作为参数调用pthread_cancle函数
12.3.5 回收已终止线程的资源
调用pthread_join函数等待其他线程终止
会阻塞,直到线程tid终止,将线程例程返回的(void*)指针赋值为thread_return指向的位置,然后回收已终止线程占用的所有存储器资源
12.3.6分离线程
Pthread_detach分离可结合线程tid
线程能够通过以pthread_self()为参数的pthread_detach调用来分离它们
12.3.7 初始化进程
Pthread_once
?
12.4多线程程序中的共享变量
12.4.1 线程存储器模型
一组并发进程运行在一个进程的上下文中
每个线程都有它自己独立的线程上下文
12.4.2 将变量映射到存储器
全局变量是定义在函数之外的变量
本地自动变量就是定义在函数内部但是没有static属性的变量
本地静态变量是定义在函数内部并有static属性的变量
12.4.3 共享变量
一个变量v是共享的,当且仅当它的一个实例被一个以上的变量引用
?
12.5 用信号量同步线程
一般而言,没有办法预测操作系统是否将为线程选择一个正确的顺序,可以借助进度图来阐明
12.5.1 进度图
进度图将n个并发线程的执行模型化为一条n维笛卡儿空间中的轨迹线
每条轴k对应于进程k的进度
每个点I代表进程已经完成了指令I这一状态
图的原点对应于没有任何线程完成一条指令的初始状态
进度图将指令执行模型化为一种状态到另一种状态的转换
合法的转换是向右或者向上的
操作共享变量cnt内容的指令(L,U,S)构成了一个临界区
两个临界区的交集称为不安全区
绕开不安全区的轨迹线叫做安全轨迹线,反之,叫做不安全轨迹线
?
12.5.2 信号量
信号量s是具有非负整数的全局变量,只能由两种操作处理。
P(s):如果s是非零的,那么P将s减1返回,如果s为零,就挂起
V(s):将s加1,如果有任何线程阻塞在P操作等待s变成非零,那么V操作会重启线程中的一个,然后s减1
?
12.5.3 使用信号量来实现互斥
基本思想:将每个共享变量与一个信号量s联系起来,用P和V操作将临界区包围起来
二元信号量的值总是0或1
以提供互斥为目的的信号量也称为互斥锁
P:加锁 V:解锁
一个被用作一组可用资源的计数器的信号量称为计数信号量
关键思想:创建禁止区
?
12.7 其他并发问题
12.7.1 线程安全
四个线程不安全函数类:
12.7.2 可重入性
可重入函数:当它们被多个线程调用时,不会引用任何共享数据
12.7.3 在线程化的程序中使用已存在的库函数
见p693图12-39
除了rand和strtok,所以这些线程不安全函数都是第3类的
12.7.4 竞争
当一个程序的正确性依赖于一个线程要在另一个线程到达y点之前到达它的控制流中的x点,就会发生竞争
12.7.5 死锁
信号量引入运行时的错误,叫做死锁
一组线程被阻塞,等待一个永远也不会为真的条件
互斥锁加锁顺序规则:如果对于程序中每对互斥锁(s,t),给所有的锁分配一个全序,每个线程按照这个顺序来请求锁,并且按照逆序来释放,那么这个程序就是无死锁的。
参考资料:《深入理解计算机系统》
?
?
标签:
原文地址:http://www.cnblogs.com/javablack/p/5024192.html