第十三周(11.30-12.06):
一、学习目标 1. 掌握三种并发的方式:进程、线程、I/O多路复用 2. 掌握线程控制及相关系统调用 3. 掌握线程同步互斥及相关系统调用 二、学习资源 1. 教材:第十一章《网络编程》简单过一下 2. 教材:第十二章《并发编程》
并发编程
如果逻辑控制流在时间上重叠,那么他们就是并发的,这种常见的现象叫做并发。
并发不限制于内核:
- 访问慢速I/O 设备
- 与人交互
- 通过推迟工作以降低延迟
- 服务多个网络客户端
- 在多核机器上进行并发计算
- 进程
- I/O多路复用
- 线程
12.1基于进程的并发编程
- 第一步:服务器接受客户端的连接请求
- 第二步:服务器派生一个子进程为这个客服端服务
- 第三步:服务器接受另一个连接请求
- 第四步:服务器派生另一个子进程为新的客户端服务
12.1.2关于进程的优劣
- 模型:共享文件表,但是不共享用户地址空间
- 独立的地址空间使得进程共享状态信息变得更加困难
基于I/O多路复用的并发编程
对于两个事件:
- 网络客户端发起的连接请求
- 用户在键盘上输入命令行
针对不知道等待哪个事件,一个解决办法就是I/O多路复用技术。
基本思路就是使用select函数,要求内核挂起进程,只有在一个或者多个I/O事件发生后,才将控制返回给应用程序,就像P651事例一样:
- 当集合{0,4}中任意描述符准备好读时返回。
- 当集合{1,2,7}中任意描述符准备好写时返回
- 如果在等待一个I/O事件发生时过了152.13秒,就超时
12.2.1基于I/O多路复用的鬓发事件驱动服务器
I/O多复用可以做并发事件驱动程序的基础,在时间驱动程序中,流是因为某种时间而前进的。
一般概念是将逻辑流模型化为状态机。
I/O多路复用技术的优劣
优点:
- 它比基于进程的设计给了程序员更好的对程序行为的控制
- 一个基于I/O多路复用的时间驱动服务器是运行在单一进程上下文中的
缺点:
- 编码复杂
12.3基于进程的并发编程
基于线程是两种方法的混合:
- 每个流使用单独的进程
- 穿件自己的逻辑流,并利用I/O多路复用来显示地调度流
线程:就是运行在进程上下文中的逻辑流
12.3.1线程的执行模型
多线程的执行模型在某些方面和多进程的执行模型是相似的。见P658图
每个进程开始生命周期时都是单一线程,这个线程称为主线程。
在某个时刻,主线程创建一个对等线程从这个时间点开始,;两个线程就并发地运行
最后,因为主线程执行一个慢速系统调用。
对等线程会执行一段时间然后控制传递回归线程,一次类推
12.3.2Posix线程
Posix线程是在C程序中处理线程的一个标准接口。
12.3.3创建线程
线程通过调用pthread_create函数来创建其他线程
12.3.4终止线程
一个线程是以一下方式之一来终止的:
- 当顶层的线程例程返回时,线程会隐式地终止
- 通过调用pthread_exit函数时,线程会显式地终止。如果主线程调用pthread_exit,它会等待所有其他对线程终止,然后再终止主线程和整个进程,返回值为thread_return.
- 某个对等线程调用Unix的exit函数,该函数终止进程以及所有与该进程相关的线程
- 另一个对等线程通过以当前线程ID作为参数调用pthread_cancle函数来终止当前线程
12.3.5回收已终止线程的资源
pthread_join函数会阻塞,直达线程tid终止,将线程例程返回的(void*)指针赋值为thread_return指定的位置,然后回收已终止线程占用的所有存储资源。
12.3.6分离线程
在任何一个时间点上,线程是可结合的或者是分离的
一个可结合的线程能够被其他线程回收其资源和杀死
12.3.7初始化线程
Pthread_once函数允许你初始化与线程例程相关的状态。
12.3.8一个基于线程的并发服务器
12.4多线程程序中的共享变量
从一个程序员的角度来看,线程很有吸引力的一个方面就是多个线程很容易共享相同的程序变量
12.4.1线程存储器模型
一组并发线程运行在一个进程的上下文中
每个线程都有它独立的上下文,包括线程ID、栈、栈指针、程序计数器和通用的目的计数器。
从实际的操作的角度来说,让一个线程去读或者写另一个线程的寄存器值是不可能的
各自地理的线程栈的存储器不是那么整齐清楚的,这些栈被保存在虚拟地址空间的栈区域中,并且通常是被相应的线程独立地访问的
12.4.2将变量映射到存储器
- 全局变量
- 本地自动变量
- 本地静态变量
12.4.3共享变量
我们说的一个变量V是共享的,当且仅当它的一个实例被一个以上的线程引用。
12.5用信号量同步线程
共享信号量是十分方便的,但是它们也引入了同步错误的可能性。
一般而言,你没有办法预测操作系统是否将为你的线程选择一个正确的顺序。
12.5.1进度图
进度图将n个并发线程的执行模型化为一条n维笛卡尔空间中的轨迹线。
每一条轴K对应线程K的进度,每个点(I1,...In)代表线程已经完成了指令Ik的这一状态。
12.5.2信号量
- P(s):如果s是非零的,那么P将s减1,并且立即返回。如果s为零,那么久挂起这个线程,知道s变为非零,而一个V操作会重启这个线程。在重启之后,P嘈操作将s减1,并将控制返回给调用者。
- V(s):V操作将s加1.如果有任何线程阻塞在p操作等待s变成非零,那么V操作会重启这些线程中的
一个,然后该线程将s减1,完成它的P操作。
12.5.3使用信号量来实现互斥
使用信号量进行互斥。s<0的不可行状态定义了一个禁止区,禁止区完全包括了不安全区,阻止了实际可行的轨迹线接触不到的安全区。
12.5.4利用信号量来调度共享资源
除了提供互斥外,信号量的另一个重要作用是调度对共享资源的访问。
- 1.生产者-消费者问题
- 2.读者-写者问题
12.5.5综合:基于预线程化的并发服务器
12.6使用线程提高并行性
几个需要知道的比
- 加速比:Sp=T1/Tp
- 效率:Ep=Sp/p=T1/pTp
12.7其他并发问题
12.7.1线程安全
- 第1类:不保护共享变量的函数
- 第2类:保持跨越多个调用的状态函数
- 第3类:返回指向静态变量的指针函数
- 第4类:调用线程不安全函数的函数
12.7.2可重入性
有一类重要的线程安全函数,叫做可重入函数,其特点在于它们具有这样的一种属性:
当他们被多个线程调用时候、,不会引用任何共享数据。
12.7.3在线程化的程序中使用已存在的库函数
大多数Unix函数,包括定义在标准C语言库中的函数都是线程安全的,只有小部分是例外。
因此,Unix系统提供大多数线程不安全函数的可重入斑斑。
12.7.4竞争
当一个程序的正确性依赖于一个线程要在另一个线程到达y点之前到达它的控制流中的x点时,就会发生竞争。
12.7.5死锁
- 互相锁加锁顺序规则:
如果对于程序中每对互斥锁(s,t),每个同时占用s和t的线程都按照相同的顺序对它们加锁,那么这个程序就是无死锁的。
12.8小结
一个并发程序是由在时间上重叠的一组逻辑流组成的。
三种不同的构建并发程序的机制:
- 进程
- I/O多路复用
- 线程
来源:https://www.cnblogs.com/20135314ZHU/p/5010752.html