引入理由
我们看一个例子,一个web
服务器的工作方式
可以看到每次从磁盘读取的时候进程都是暂停的,这样会导致性能低下
那如何提高服务器的工作效率?通常情况下是使用网页缓存
在
没有线程情况下的两种解决方案
I/O
多线程的解决方式
**说明:**这是一个多线程的web
服务器的工作方式,首先读取客户端的请求,之后由分派线程将各个任务分派给工作线程,这里还是采用了网页缓存
于是我们可以看到一个web
服务器的实现有三种方式:
如果有多个处理器的话,一个进程就会有多个线程同时在执行了,这样可以极大的提高运行性能
线程的属性
ID
-->
需要提供一些操作一般有三种实现机制
说明:线程是由运行时系统管理的,在内核中只有进程表。典型例子就是UNIX
undefinedPOSIX线程库–PTHREAD
6.3.3 混合模型
Solaris
操作系统0)新建:创建后尚未启动的线程处于这种状态。 1)运行:包括了 OS 中 Running 和 Ready 状态,也就是处于此状态的线程可能正在运行,也可能正在等待 cpu 为它分配执行时间 2)无限期等待:处于这种状态的线程不会被分配 cpu 执行时间,要等待其他线程显示唤醒。以下方法会让线程进入无限期等待 :
3)有限期的等待:处于这种状态的线程也不会被分配 cpu 执行时间,不过无需等待被其他线程显示唤醒,而是在一定时间后,他们会由 OS 自动唤醒 1.设置了 timeout 的 object.wait() 方法 2. 设置了 timeout 参数的 Thread.join() 3.LockSupport.parkNanos()
4.LockSupport.parkUnit()
4) 阻塞:与"等待"的区别:
一种程序结构,结构内的多个子程序(对象 “对象 (计算机科学)”)或模块 “模块 (程序设计)”))形成的多个工作线程 “工作 (信息学)”)互斥访问共享资源。
这些共享资源一般是硬件设备或一群变量。
管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。与那些通过修改数据结构实现互斥访问的并发程序设计相比,管程实现很大程度上简化了程序设计。
管程提供了一种机制,线程可以临时放弃互斥访问,等待某些条件得到满足后,重新获得执行权恢复它的互斥访问。
①如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入 ②任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待 ③进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区 ④如果进程不能进入自己的临界区,则应让出CPU,避免进程出现“忙等”
一组进程中,每个进程都无限等待被该组进程中另一进程所占用的资源,因而永远无法得到的资源,这种现象称为进程死锁,这一组进程就称为死锁进程。
如果发生死锁,会浪费大量系统资源,甚至导致系统崩溃,注意:
资源数量有限、锁和信号量错误使用。
“申请-分配-使用-释放”模式
I/O
部件、内存、文件、数据库、信号量等可抢占资源。说明:
1
和资源2
。比如这两个进程都上cpu
执行,但是进程A
执行到第二句的时候需要使用资源2
,而进程B
执行到第二句的时候需要资源1
,但是此时恰好都不能获得各自的资源,这样就进入忙等待(进入轮询看资源是否可用),这就是活锁,也就是先加锁再轮询,这样导致两个进程既无进展也没有阻塞。这和死锁的概念的区别在于死锁的时候进程不能进入cpu
去执行。存在一个进程等待队列{P1,P2,......,Pn}
,其中P1
等待P2
占有的资源,P2
等待P3
占有的资源,…,Pn
等待P1
占有的资源,形成一个进程等待环路。
用有向图描述系统资源和进程的状态
化简步骤:
死锁检测和解除
SPooling
技术的引入,解决不允许任何进程直接占有打印机的问题。设计一个“守护进程/线程”负责管理打印机,进程需要打印时, 将请求发给该daemon
,由它完成打印任务。当一个进程申请的资源被其他进程占用时,可以通过操作系统抢占这一资源(两个进程优先级不同)
只适用于状态易于保存和恢复的对主存资源和处理器资源的分配适用于资源。如cpu
和内存等。
P1
申请了资源1、3、9
,而进程P2
需要资源1、2、5
,那么进程P2
在申请时必须按照1、2、5
的顺序来申请,这样就破坏了环路条件,因为在申请到资源1
之前,后面的资源是申请不到的。存在下述严重问题:
限制了新类型设备的增加。
造成对资源的浪费。
必然会限制用户简单、自主地编程。
P1,P2,......,Pn
,则称系统处于安全状态。安全状态表示系统一定没有发生死锁。{P1,P2,......,Pn}
是安全的,如果对于每个进程Pi(1<= i <= n)
:它以后还需要的资源数量不超过系统当前剩余资源量与所有进程Pj(j < i)
当前占有资源量只和。Dijkstra
在1965
年提出的,是仿照银行发放贷款时采取的控制方式而设计的一种死锁避免算法。
当进程Pi
提出资源申请时,系统执行下列步骤:
(1)若Request[i] <= Need[i]
,转(2);否则,报错返回。
(2)若Request[i] <= Available
,转(3);否则,报错返回。
(3)假设系统分配了资源,则有:
Available = Available - Request[i];
Allocation[i] = Allocation[i] + Request[i];
Need[i] = Need[i] = Request[i]
`</pre>
若系统新状态是安全的,则分配完成;若系统新状态是不安全的,则恢复原来状态,进程等待。
为了进行安全性检查,定义了数据结构:
安全性检查的步骤:
(1)`Work = Available; Finish = false;`
(2)寻找满足条件的`i`:
如果不存在,则转(4)
(3)
`Work = Work + Allocationi ;
转(2)
(4)若对所有i,Finish[i] == true
,则系统处于安全状态,否则,系统处于不安全状态。
允许死锁发生,但是操作系统会不断监视系统进展情况,判断死锁是否真的发生。一旦死锁发生则采取专门的措施,解除死锁并以最小的代价恢复操作系统运行。
1、当进程由于资源请求不满足而等待时检测死锁。这里缺点是系统开销较大。
2、定时检测
3、系统资源利用率下降时检测死锁
发生死锁后重要的是以最小的代价恢复系统的运行。方法如下: