进程、join方法、守护进程、互斥锁
- 去幕布 >>
- 操作系统发展史
- 发展史
1. 1946年第一台计算机诞生--20世纪50年代中期,计算机工作还在采用手工操作方式。此时还没有操作系统的概念。
2. 20世纪50年代后期,出现人机矛盾:手工操作的慢速度和计算机的高速度之间形成了尖锐矛盾,
手工操作方式已严重损害了系统资源的利用率(使资源利用率降为百分之几,甚至更低),不能容忍。
3. 唯一的解决办法:只有摆脱人的手工操作,实现作业的自动过渡。这样就出现了成批处理。 - 批处理
联机批处理系统(即作业的输入/输出由CPU来处理,例如 通过磁带)脱机批处理系统(I/O--卫星机--高速磁带--主机) - 多道技术
- 什么是多道技术
即同时把多个程序放入内存,并允许它们交替在CPU中运行,它们共享系统中的各种硬、软件资源。
当一道程序因I/O请求而暂停运行时,CPU便立即转去运行另一道程序。 - 多道技术的使用
1. 空间上的复用多个程序公用一套计算机硬件
2.时间上的复用(切换+保存状态)
a.当一个程序遇到 I/O操作,操作系统会剥夺该程序的CPU执行权限 (提高CPU利用率,且不影响程序执行效率)
b.当一个程序长时间占用CPU,操作系统会剥夺改程序的 CPU执行权限 (降低程序执行效率)
- 什么是多道技术
- 多道技术之后的发展
多道程序系统的出现,标志着操作系统渐趋成熟的阶段,
先后出现了作业调度管理、处理机管理、存储器管理、外部设备管理、文件系统管理等功能。
由于多个程序同时在计算机中运行,开始有了空间隔离的概念,只有内存空间的隔离,才能让数据更加安全、稳定。
除了空间隔离之外,多道技术还第一次体现了时空复用的特点,遇到IO操作就切换程序,使得cpu的利用率提高了,计算机的工作效率也随之提高。 - 分时系统、实时系统、通用操作系统
- 个人计算机操作系统--网络操作系统--分布式操作系统
- 操作系统
- 理解
操作系统就是一个协调、管理和控制计算机硬件资源和软件资源的控制程序 - 作用
1. 隐藏了丑陋的硬件调用接口,为应用程序员提供调用硬件资源的 更简单,更清晰的模型(系统调用接口)
2. 将应用程序对硬件资源的竞态请求变得有序化
- 理解
- 发展史
- 进程
- 什么是进程
进程是一个执行中的程序进程是一个实体,每一个进程都有自己的地址空间 - 进程和程序的异同
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
程序可以作为一种软件资料长期存在,而进程是有一定生命期的。
程序是永久的,进程是暂时的。 - 进程调度的方法
1.先来先服务 先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。 FCFS算法比较有利于长作业(进程),而不利于短作业(进程)。 由此可知,本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业(进程)。 2.短作业优先 短作业(进程)优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度。 但其对长作业不利;不能保证紧迫性作业(进程)被及时处理;作业的长短只是被估算出来的。 3.时间轮转发 时间片轮转(Round Robin,RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。 在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片,例如,几十毫秒至几百毫秒。 如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,等待下一次调度。 同时,进程调度程序又去调度当前就绪队列中的第一个进程。 在轮转法中,加入到就绪队列的进程有3种情况: 一种是分给它的时间片用完,但进程还未完成,回到就绪队列的末尾等待下次调度去继续执行。 另一种情况是分给该进程的时间片并未用完,只是因为请求I/O或由于进程的互斥与同步关系而被阻塞。当阻塞解除之后再回到就绪队列。 第三种情况就是新创建进程进入就绪队列。 4.多级反馈队列 前面介绍的各种用作进程调度的算法都有一定的局限性。 如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,则短进程优先和基于进程长度的抢占式调度算法都将无法使用。 而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间, 而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。 在采用多级反馈队列调度算法的系统中,调度算法的实施过程如下所述。 (1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。 第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。 该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。 例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。 (2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。 当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统; 如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行; 如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。 (3) 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行; 仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。 如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列), 则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。
- 多级反馈队列图解
- 进程的并行与并发
并发:看起来像同时运行就可以
并行:真正意义上的同时执行 单核计算机不能实现并行,可以实现并发 - 进程的阻塞非阻塞
阻塞非阻塞:表示程序的运行状态
阻塞:阻塞态( I/O操作,文件操作,sleep都能使运行-->阻塞态)
非阻塞:就绪态、运行态 - 程序三态转换图
- 程序的三态代码示例
- 程序的同步异步
同步异步:表示程序的提交方式
同步:任务提交后,原地等待任务的执行,并拿到返回结果才走,期间不做任何事(程序层面的表现就是卡住了)
异步:任务提交后,不在原地等待,二十继续执行下一行代码(结果是要的,但是通过其他方式获取)
- 什么是进程
- 创建进程的两种方式
- 理解创建进程
创建进程就是在内存中重新开辟一块内存空间,将运行产生的代码丢进去,
一个进程对应在内存就是一块独立的内存空间,
进程与进程之间数据是隔离的,无法直接交互,但是可以通过某些技术实现间接交互 - 第一种(直接使用mutiprocessing的Process模块)
from multiprocessing import Process import time def test(name): print('%s is running'%name) time.sleep(3) print('%s is over'%name) """ windows创建进程会将代码以模块的方式 从上往下执行一遍 linux会直接将代码完完整整的拷贝一份 windows创建进程一定要在if __name__ == '__main__':代码块内创建 否则报错 """ if __name__ == '__main__': p = Process(target=test,args=('egon',)) # 创建一个进程对象 p.start() # 告诉操作系统帮你创建一个进程 print('主')
- 第二种(继承Process,并覆盖__init__方法,再重写一个run方法)
# 创建进程的第二种方式 from multiprocessing import Process import time class MyProcess(Process): def __init__(self,name): super().__init__() self.name = name def run(self): print('%s is running' % self.name) time.sleep(3) print('%s is over' % self.name) if __name__ == '__main__': p = MyProcess('egon') p.start() print('主')
- Process模块
Process模块用来创建进程 一般需要传入target目标函数,args函数的参数 强调: 1. 需要使用关键字的方式来指定参数 2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号 参数介绍: 1 group参数未使用,值始终为None 2 target表示调用对象,即子进程要执行的任务 3 args表示调用对象的位置参数元组,args=(1,2,'egon',) 4 kwargs表示调用对象的字典,kwargs={'name':'egon','age':18} 5 name为子进程的名称 方法介绍: 1 p.start():启动进程,并调用该子进程中的p.run() 2 p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法 3 p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁 4 p.is_alive():如果p仍然运行,返回True 5 p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程 属性名称: 1 p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置 2 p.name:进程的名称 3 p.pid:进程的pid 4 p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可) 5 p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可) 查看进程pid: os.getpid() 查看父进程pid:os.ppid()
- 理解创建进程
- join方法
join 保证所有子进程执行完,主进程才能工作,不然一直阻塞 from multiprocessing import Process import time def test(name,i): print('%s is running'%name) time.sleep(i) print('%s is over'%name) if __name__ == '__main__': p_list = [] p = Process(target=test,args=('egon',1)) p1 = Process(target=test,args=('kevin',2)) p2 = Process(target=test,args=('jason',3)) start_time = time.time() p.start() # 仅仅是告诉操作系统帮你创建一个进程 至于这个进程什么时候创 操作系统随机决定 p1.start() p2.start() p2.join() p.join() p1.join() # 主进程代码等待子进程运行结束 才继续运行 # p.join() # 主进程代码等待子进程运行结束 print('主') print(time.time() - start_time) '''执行结果 jason is running egon is running kevin is running egon is over kevin is over jason is over 主 3.094853162765503 '''
- 进程间数据是隔离的
''' 本例子:父进程是说pycharm执行这个py文件就创建了一个进程 py文件的代码中通过Process模块创建了一个进程, 那么从关系上来讲pycharm执行而创建出的就是父进程,Process创建的就是子进程 ''' from multiprocessing import Process import time money = 100 def test(): global money money = 99999999 if __name__ == '__main__': p = Process(target=test) p.start() p.join() print(money) # 执行结果:100
- 僵尸进程与孤儿进程
- 父进程回收子进程资源的两种方式
1.join方法
2.父进程正常死亡 所有进程都会步入僵尸进程 - 孤儿进程
子进程没死 父进程意外死亡
针对linux会有儿童福利院(init)
如果父进程意外死亡他所创建的子进程都会被福利院收养
- 父进程回收子进程资源的两种方式
- 守护进程
- 理解
会随着主进程的结束而结束。 主进程创建守护进程 其一:守护进程会在主进程代码执行结束后就终止 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children 注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
- 代码示例
from multiprocessing import Process import time def test(name): print('%s总管正常活着'%name) time.sleep(3) print('%s总管正常死亡'%name) if __name__ == '__main__': p = Process(target=test,args=('egon',)) p.daemon = True # 将该进程设为守护进程 这句话必须放在start语句之前 否则报错 p.start() time.sleep(0.1) print('皇帝jason寿正终寝')
- 理解
- 互斥锁
- 为什么有互斥锁
当多个进程操作同一份数据的时候,会造成数据的错乱,这个时候必须加锁处理
本质是:将并发变成串行。虽然降低了效率,但是提高了数据的安全 - 注意事项
1. 锁不要轻易使用 容易造成死锁现象
2. 只在处理数据的部分加锁 不要在全局加锁
3. 锁必须在主进程中产生 交给子进程去使用 - 代码示例
from multiprocessing import Process,Lock import time, json # 查票 def search(i): with open('data','r',encoding='utf-8') as f: data = f.read() t_d = json.loads(data) print('用户%s查询余票为:%s'%(i,t_d.get('ticket'))) # 买票 def buy(i): with open('data','r',encoding='utf-8') as f: data = f.read() t_d = json.loads(data) time.sleep(1) if t_d.get('ticket') > 0: # 票数减一 t_d['ticket'] -= 1 # 更新票数 with open('data','w',encoding='utf-8') as f: json.dump(t_d,f) print('用户%s抢票成功'%i) else: print('没票了') def run(i,mutex): search(i) mutex.acquire() # 抢锁 只要有人抢到了锁 其他人必须等待该人释放锁 buy(i) mutex.release() # 释放锁 if __name__ == '__main__': mutex = Lock() # 生成了一把锁 for i in range(3): p = Process(target=run,args=(i,mutex)) p.start() '''执行结果: 用户0查询余票为:0 用户2查询余票为:0 用户1查询余票为:0 没票了 没票了 没票了 '''
- 为什么有互斥锁