上海 day30--并发编程、进程
2019-08-11 09:39 在上海的日子里 阅读(135) 评论(0) 编辑 收藏 举报目 录
一、操作系统发展史
多道程序设计技术
多道技术的特点
1.空间上的复用 多个程序共用一套计算机硬件 2.时间上的复用 切换+保存状态 1.当一个程序遇到IO操作 操作系统会剥夺该程序的cpu执行权限(提高了cpu的利用率 并且也不影响程序的执行效率) 2.当一个程序长时间占用cpu 操作系统也会剥夺该程序的cpu执行权限(降低了程序的执行效率)
图示:
二、进程相关理论
什么是进程?
狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
什么是程序?
一坨代码
程序:一坨代码
进程:正在运行的程序
进程与程序的区别是什么?
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
程序可以作为一种软件资料长期存在,而进程是有一定生命期的。
程序是永久的,进程是暂时的。
进程的调度?
要想多个进程交替运行,操作系统必须对这些进程进行调度,这个调度也不是随即进行的,而是需要遵循一定的法则,由此就有了进程的调度算法。
先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。FCFS算法比较有利于长作业(进程),而不利于短作业(进程)。由此可知,本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业(进程)。
短作业(进程)优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度。但其对长作业不利;不能保证紧迫性作业(进程)被及时处理;作业的长短只是被估算出来的。
时间片轮转(Round Robin,RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片,例如,几十毫秒至几百毫秒。如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,等待下一次调度。同时,进程调度程序又去调度当前就绪队列中的第一个进程。 显然,轮转法只能用来调度分配一些可以抢占的资源。这些可以抢占的资源可以随时被剥夺,而且可以将它们再分配给别的进程。CPU是可抢占资源的一种。但打印机等资源是不可抢占的。由于作业调度是对除了CPU之外的所有系统硬件资源的分配,其中包含有不可抢占资源,所以作业调度不使用轮转法。 在轮转法中,时间片长度的选取非常重要。首先,时间片长度的选择会直接影响到系统的开销和响应时间。如果时间片长度过短,则调度程序抢占处理机的次数增多。这将使进程上下文切换次数也大大增加,从而加重系统开销。反过来,如果时间片长度选择过长,例如,一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕,则轮转法变成了先来先服务法。时间片长度的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的。 在轮转法中,加入到就绪队列的进程有3种情况: 一种是分给它的时间片用完,但进程还未完成,回到就绪队列的末尾等待下次调度去继续执行。 另一种情况是分给该进程的时间片并未用完,只是因为请求I/O或由于进程的互斥与同步关系而被阻塞。当阻塞解除之后再回到就绪队列。 第三种情况就是新创建进程进入就绪队列。 如果对这些进程区别对待,给予不同的优先级和时间片从直观上看,可以进一步改善系统服务质量和效率。例如,我们可把就绪队列按照进程到达就绪队列的类型和进程被阻塞时的阻塞原因分成不同的就绪队列,每个队列按FCFS原则排列,各队列之间的进程享有不同的优先级,但同一队列内优先级相同。这样,当一个进程在执行完它的时间片之后,或从睡眠中被唤醒以及被创建之后,将进入不同的就绪队列。
前面介绍的各种用作进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,则短进程优先和基于进程长度的抢占式调度算法都将无法使用。 而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。在采用多级反馈队列调度算法的系统中,调度算法的实施过程如下所述。 (1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。 (2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。 (3) 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。
图示:
进程的并行与并发?
官方解释:
并行 : 并行是指两者同时执行,比如赛跑,两个人都在不停的往前跑;(资源够用,比如三个线程,四核的CPU )
并发 : 并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率。
通俗解释:
并发:看起来像同时运行的就可以
并行:真正意义上的同时执行
单核的计算机能不能实现并行,但是可以实现并发
并行与并发的区别
并行是从微观上,也就是在一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器。
并发是从宏观上,在一个时间段上可以看出是同时执行的,比如一个服务器同时处理多个session。
同步异步阻塞非阻塞
进程的三状态
进程遇到I/O操作就会进入阻塞状态
同步和异步
同步异步:表示的是任务的提交方式
同步:任务提交之后 原地等待的任务的执行并拿到返回结果才走 期间不做任何事(程序层面的表现就是卡住了)
异步:任务提交之后 不再原地等待 而是继续执行下一行代码(结果是要的 但是是用过其他方式获取)
阻塞和非阻塞
阻塞非阻塞:表示的程序的运行状态
阻塞:阻塞态
非阻塞:就绪态 运行态
注意:同步异步、阻塞非阻塞是两种不同的概念,不能混为一谈
三、创建进程的两种方式(******)
创建进程的两种方式:
1、使用Process实例化
2、继承Process重写run方法
创建进程注意事项:
# """ # windows创建进程会将代码以模块的方式 从上往下执行一遍 # linux会直接将代码完完整整的拷贝一份 # # # windows创建进程一定要在if __name__ == '__main__':代码块内创建 否则报错
以为windows是以模块导入的方式从上执行代码
'''创建进程的第一种方法''' from multiprocessing import Process import time def type(name): print('%s 快来看台风'%name) time.sleep(3) print('%s 台风过去了'%name) if __name__ == '__main__': p = Process(target=type,args=('qin',)) # 创建一个进程对象 后面有两个参数,目标函数和函数参数 p.start() # 告诉操作系统创建一个进程 print('此刻运行主进程!')
'''创建进程第二种方式''' from multiprocessing import Process import time class MyProcess(Process): def __init__(self,name): super().__init__() self.name = name def run(self): print('%s 快跑!台风来了'%self.name) time.sleep(1) print('%s 回来吧,台风早过去了'%self.name) if __name__ == '__main__': p = MyProcess('jason') p.start() print('主进程执行!')
""" 创建进程就是在内存中重新开辟一块内存空间 将允许产生的代码丢进去 一个进程对应在内存就是一块独立的内存空间 进程与进程之间数据是隔离的 无法直接交互 但是可以通过某些技术实现间接交互 """
四、进程方法join
进程方法join 的作用
——等子进程执行结束后主进程才执行
注意:
p.start() 只是告诉操作系统帮助我们创建一个进程,至于什么时候创建由操作系统随机决定
代码演示:
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 = [] # for i in range(3): # p = Process(target=test,args=('进程%s'%i,i)) # p.start() # p_list.append(p) # for p in p_list: # p.join() 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)
五、进程对象及其他方法
current_process().pid 获取当前进程的id
os.getpid() 获取当前进程的id号
os.getppid() 获取当前进程的父进程id号 (父进程就是主进程的意思)
terminate() 杀死进程
is_alive() 判断进程是否存活
代码演示如下:
'''进程对象其他方法''' from multiprocessing import Process,current_process # current_process 获得当前进程号 import time import os def run(name): # print(f'{name}正在来',current_process().pid) print(f'{name}正在来','子进程id%s'%os.getpid(),'子进程的父进程id%s'%os.getppid()) time.sleep(20) print(f'{name}结束') if __name__ == '__main__': p = Process(target=run,args=('jason',)) p.start() # print('主进程正在执行。。。',current_process().pid) print('主进程正在执行。。。','主进程id%s'%os.getpid(),'主主进程id%s'%os.getppid()) ''' 执行结果: 主进程正在执行。。。 主进程id9656 主主进程id5344 jason正在来 子进程id11464 子进程的父进程id9656 jason结束 '''
由上图可知:
程序里面主进程是py文件,即python.exe ,而py文件的主进程就是python解释器,pycharm;
如果py文件是在cmd终端运行,那么该py文件的主进程就是cmd!!!
from multiprocessing import Process,current_process import os import time def test(name): # print('%s is running'%name,current_process().pid) print('%s is running'%name,'子进程%s'%os.getpid(),'父进程%s'%os.getppid()) time.sleep(3) print('%s is over'%name) if __name__ == '__main__': p = Process(target=test,args=('egon',)) p.start() p.terminate() # 杀死当前进程 其实是告诉操作系统帮你杀死一个进程 time.sleep(0.1) print(p.is_alive()) # 判断进程是否存活 # print('主',current_process().pid) print('主',os.getpid(),'主主进程:%s'%os.getppid())
六、进程间数据互相隔离
代码验证如下:
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
七、守护进程
什么是守护进程?
会随着主进程的结束而结束。 主进程创建守护进程 其一:守护进程会在主进程代码执行结束后就终止 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children 注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
代码演示:
'''守护进程''' from multiprocessing import Process import time def run(name): print('%s 台风来了快跑!'%name) time.sleep(2) print('%s回来吧,钛粉没有了'%name) if __name__ == '__main__': p = Process(target=run,args=('zhu',)) # 创建进程对象 p.daemon = True # 必须写在p.start()之前,只有在操作系统创建进程之前才能设置守护进程 p.start() # 告诉操作系统帮助创建一个进程 time.sleep(0.1) print('主进程在这执行!') ''' 运行结果:主进程在这执行! 因为主程序最先执行完毕,所以子进程自动停止,子进程没有结果。 '''
注意:
p.daemon = True 设置守护进程,但是在操作系统创建进程之前,所以p.daemon = True 要写在 p.start()
八、互斥锁(*****)
互斥锁的引出?
互斥锁 当多个进程操作同一份数据的时候 会造成数据的错乱 这个时候必须加锁处理 将并发变成串行 虽然降低了效率但是提高了数据的安全 注意: 1.锁不要轻易使用 容易造成死锁现象 2.只在处理数据的部分加锁 不要在全局加锁 锁必须在主进程中产生 交给子进程去使用
图示重点:
锁的使用方法?
1、只在数据处理的部分使用,不要在全局加锁!
2、锁必须在主进程中产生,交给子进程去使用。
抢票小程序中互斥锁的应用
代码演示:
'''互斥锁''' from multiprocessing import Process,Lock import time import os import json def search(i): with open('data','r',encoding='utf-8') as f: data = f.read() # 数据量小时可以直接读出,当数据量大时要f.readlines() # 此时读出的data是字符串格式,将其反序列化还原成字典格式 json_d = json.loads(data) print("用户%s查询余票:%s"%(i,json_d.get('ticket'))) def buy(i): # 买票的时候也要刷新一下余票 with open('data','r',encoding='utf-8') as f: data = f.read() # 数据量小时可以直接读出,当数据量大时要f.readlines() # 此时读出的data是字符串格式,将其反序列化还原成字典格式 json_d = json.loads(data) # 模拟页面延时 time.sleep(3) # 买票 if json_d.get('ticket') > 0 : # 票数减一 json_d['ticket'] -= 1 # 刷新数据写入文件 with open('data','w',encoding='utf-8') as f: json.dump(json_d,f) print('用户%s 购票成功'%i) else: print('没有票了') def run(i,mutex): search(i) # 锁的设置:必须在数据处理部分才可以使用,不能在全局加锁 # 查票不需要加锁,只有购票的时候才需要,因此放在buy(i)上面 mutex.acquire() # 抢锁,只要有人抢到了锁其他人必须等待所释放才可以继续抢 buy(i) mutex.release() # 释放锁 if __name__ == '__main__': mutex = Lock() # 生成锁! 在主进程设置一把锁,变量名固定,并把锁传给子进程使用 for i in range(10): p = Process(target=run,args=(i,mutex)) p.start()