课程内容总结
并发:逻辑控制流在时间上是重叠的
基于进程的并发编程
例如:在父进程中接受客户端请求,然后创建新的子进程来为每个客户端服务。
- 假设我们有两个客户端和一个服务器,服务器正在监听一个监听表述符上的请求。现在假设服务器接受了客户端1的连接请求。
- 基于进程的并发服务器:
- 需要包括一个SIGCHID处理程序,来收回将死进程。
- 父子进程必须关闭它们各个的connfd拷贝。
- 因套接字的文件表项中的引用计数,直到父子进程的connfd都关闭了,到客户端的连接才会终止。
基于I/O多路复用的并发编程
- 使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。
-
select函数处理类型为fd_set的集合,也叫做描述符集合,并在逻辑上描述为一个大小为n的位向量:b_n-1,...b_1,b_0*
-
描述符:
- 分配他们
- 将一个此种类型的变量赋值给另一个变量
- 用FD_ZERO、FD_SET、FD_CLR和FD_ISSET宏指令来修改和检查它们
-
基于I/O多路复用的并发事件驱动服务器
-
比基于进程的设计给了程序员更多的对程序行为的控制。
-
一个基于I/O多路复用的事件驱动服务器是运行在单一进程上下文中的,因此每个逻辑流都能访问该进程的全部地址空间。,使得在流之间共享数据变得容易。
-
事件驱动设计常常比基于进程的设计要高效得多,因为它们不需要进程上下文切换来调度新进程。
-
基于线程的并发进程
-
这是上面两种方法的混合,结合了上面两种方法的特性。
- 和进程一样,由内核调度,并内核通过一个整数ID来识别线程。
- 同基于I/O多路复用的流一样,多个线程运行在单一进程的上下文中。因此共享这个进程的虚拟地址空间的整个内容:代码啊,数据,堆,共享库和打开文件。
- 线程就是运行在进程上下文中的逻辑流。
-
每个线程都有自己的线程上下文,包括:
- 一个唯一的整数线程ID
- 栈
- 栈指针
- 程序计数器
- 通用目的寄存器
- 条件码
- 所有运行在一个进程里的线程共享该进程的整个虚拟地址。
- 主线程:每个进程开始生命周期时都是单一线程,这个线程称为主线程,它是是进程中第一个运行的线程。
- 对等线程:被主线程创建,后与主线程并发运行。
- 对等(线程)池:一个线程可以杀死它的任何对等线程,或等待它的任何对等线程终止。每个对等线程都能读写相同的共享数据。
- Posix线程:线程代码和本地数据都被封装在一个线程例程中。每个线程都以一个通用指针作为输入,并返回一个通用指针。
- 创建线程:线程调用pthread_create函数来创建其他线程。
-
终止线程
- 顶层的线程返回,隐式终止。
- 调用pthread_exit函数,显示终止。(会等待所有其他对等线程终止,然后再终止主线程和整个进程,返回thread return)
- 某个对等线程调用Unix的exit函数,该函数终止进程以及所有与该进程相关的线程。
- 另一个对等线程以当前线程ID作为参数调用pthread_cancle终止当前进程。
- 线程调用pthread_join函数等待其他线程终止。这个函数会阻塞,直到线程tid终止,将线程例程返回的(void*)指针赋值为thread_return指向的位置,然后回收已终止线程占用的所有存储器资源.
- 分离线程:在任何一个时间点上,进程是可结合的或分离的。
-
初始化进程:调用pthread_once来初始化与线程的相关状态;
多线程程序中的共享变量
- 根据存储类型被映射到虚拟存储器。
- 全局变量。函数以外的变量。运行时,虚拟存储器的读/写区域只包含每个全局变量的 一个实例,任何线程可用。
- 本地自动变量定义在函数内部没有static属性的变量。运行时,每个线程都含有自己的所有本地自动变量实例
- 本地静态变量。在函数内部,有static。和全局变量一样,虚拟存储器的读/写区域只包含在程序中声明的本地静态变量的一个实例。
- 共享变量:说一个变量v是共享的,当且仅当它的一个实例被一个以上的线程引用。
-
进度图
进度图是将n个并发线程的执行模型化为一条n维笛卡尔空间中的轨迹线。
将指令执行模型为一种状态到另一种状态的转换。
合法转换:向右或向上。
点(L1,S2)对应线程完成L1,而线程2完成S2状态
-
信号量:是用信号量解决同步问题,信号量s是具有非负整数值的全局变量,有两种特殊的操作来处理,称为P和V:
-
P(s):如果s非零,那么P将s减1,并且立即返回。
-
V(s):V操作将s加1,并重启一个阻塞的线程
-
- 使用信号量来实现互斥:将每个共享变量(或者一组相关的共享变量)与一个信号量s(初始为1)联系起来,然后用P(s)和V(s)操作将相应的临界区包围起来。
- 利用信号量来调度共享资源
-
生产者-消费者问题:因为插入和取出项目都涉及更新共享变量,所以要保证:对缓冲区的访问是互斥的。
-
读者-写者问题:修改对象的线程叫做写者;只读对象的线程叫做读者。写者必须拥有对对象的独占访问,而读者可以和无限多个其他读者共享对象。
-
- 基于预防线程话的并发服务器
-
使用线程提高并行性:多核处理可以并行。
其他并发问题
-
不安全函数类
- 不保护共享变量的函数
- 保持跨越多个调用的状态的函数
- 返回指向静态变量的指针的函数
- 调用线程不安全函数的函数
- 可重入函数:被多个线程调用时,不会引用任何共享数据。(是线程安全函数的一个真子集)
-
显式可重入的:所有函数参数都是传值传递,没有指针,并且所有的数据引用都是本地的自动栈变量,没有引用静态或全剧变量。
-
隐式可重入的:调用线程小心的传递指向非共享数据的指针。
-
- 竞争: 程序员假定线程会按照某种特殊的轨迹穿过执行状态空间,忘了一条准则规定:线程化的程序必须对任何可行的轨迹线都正确工作。
-
死锁:一组线程被阻塞了,等待一个永远也不会为真的条件。