python之路——进程
操作系统背景知识
顾名思义,进程即正在执行的一个过程。进程是对正在运行程序的一个抽象。
进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一。操作系统的其他所有内容都是围绕进程的概念展开的。
所以想要真正了解进程,必须事先了解操作系统,点击进入
PS:即使可以利用的cpu只有一个(早期的计算机确实如此),也能保证支持(伪)并发的能力。将一个单独的cpu变成多个虚拟的cpu(多道技术:时间多路复用和空间多路复用+硬件上支持隔离),没有进程的抽象,现代计算机将不复存在。
必备的理论基础:
#一 操作系统的作用:
1:隐藏丑陋复杂的硬件接口,提供良好的抽象接口
2:管理、调度进程,并且将多个进程对硬件的竞争变得有序 #二 多道技术:
1.产生背景:针对单核,实现并发
ps:
现在的主机一般是多核,那么每个核都会利用多道技术
有4个cpu,运行于cpu1的某个程序遇到io阻塞,会等到io结束再重新调度,会被调度到4个
cpu中的任意一个,具体由操作系统调度算法决定。 2.空间上的复用:如内存中同时有多道程序
3.时间上的复用:复用一个cpu的时间片
强调:遇到io切,占用cpu时间过长也切,核心在于切之前将进程的状态保存下来,这样
才能保证下次切换回来时,能基于上次切走的位置继续运行
什么是进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。[3]
进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。 从理论角度看,是对正在运行的程序过程的抽象;
从实现角度看,是一种数据结构,目的在于清晰地刻画动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序。 动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
结构特征:进程由程序、数据和进程控制块三部分组成。
多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。 程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
程序可以作为一种软件资料长期存在,而进程是有一定生命期的。
程序是永久的,进程是暂时的。
注意:同一个程序执行两次,就会在操作系统中出现两个进程,所以我们可以同时运行一个软件,分别做不同的事情也不会混乱。
进程调度
要想多个进程交替运行,操作系统必须对这些进程进行调度,这个调度也不是随即进行的,而是需要遵循一定的法则,由此就有了进程的调度算法。
先来先服务(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。
同步异步阻塞非阻塞
状态介绍
在了解其他概念之前,我们首先要了解进程的几个状态。在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪,运行和阻塞。
(1)就绪(Ready)状态
当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。
(2)执行/运行(Running)状态当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。
(3)阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。
同步和异步
所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列
。要么成功都成功,失败都失败,两个任务的状态可以保持一致。
所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了
。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列
。
阻塞与非阻塞
阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的
继续上面的那个例子,不论是排队还是使用号码等待通知,如果在这个等待的过程中,等待者除了等待消息通知之外不能做其它的事情,那么该机制就是阻塞的,表现在程序中,也就是该程序一直阻塞在该函数调用处不能继续往下执行。
相反,有的人喜欢在银行办理这些业务的时候一边打打电话发发短信一边等待,这样的状态就是非阻塞的,因为他(等待者)没有阻塞在这个消息通知上,而是一边做自己的事情一边等待。 注意:同步非阻塞形式实际上是效率低下的,想象一下你一边打着电话一边还需要抬头看到底队伍排到你了没有。如果把打电话和观察排队的位置看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的;而异步非阻塞形式却没有这样的问题,因为打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换。
例子
同步/异步与阻塞/非阻塞
- 同步阻塞形式
效率最低。拿上面的例子来说,就是你专心排队,什么别的事都不做。
- 异步阻塞形式
如果在银行等待办理业务的人采用的是异步的方式去等待消息被触发(通知)
,也就是领了一张小纸条,假如在这段时间里他不能离开银行做其它的事情,那么很显然,这个人被阻塞在了这个等待的操作上面;
异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。
- 同步非阻塞形式
实际上是效率低下的。
想象一下你一边打着电话一边还需要抬头看到底队伍排到你了没有,如果把打电话和观察排队的位置看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换
,效率可想而知是低下的。
- 异步非阻塞形式
效率更高,
因为打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换
。
比如说,这个人突然发觉自己烟瘾犯了,需要出去抽根烟,于是他告诉大堂经理说,排到我这个号码的时候麻烦到外面通知我一下,那么他就没有被阻塞在这个等待的操作上面,自然这个就是异步+非阻塞的方式了。
很多人会把同步和阻塞混淆,是因为很多时候同步操作会以阻塞的形式表现出来
,同样的,很多人也会把异步和非阻塞混淆,因为异步操作一般都不会在真正的IO操作处被阻塞
。
进程的创建与结束
进程的创建
进程的创建需要用到if __name__ =='__main__':来进行设置,因为你不这样设置就会一直在递归无限创建那么就会造成系统卡死,所以必须用这个。 (仅限制windows上的需要)
但凡是硬件,都需要有操作系统去管理,只要有操作系统,就有进程的概念,就需要有创建进程的方式,一些操作系统只为一个应用程序设计,比如微波炉中的控制器,一旦启动微波炉,所有的进程都已经存在。
而对于通用系统(跑很多应用程序),需要有系统运行过程中创建或撤销进程的能力,主要分为4中形式创建新的进程:
1. 系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关,运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印)
2. 一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)
3. 用户的交互式请求,而创建一个新进程(如用户双击暴风影音)
4. 一个批处理作业的初始化(只在大型机的批处理系统中应用)
无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的。
1. 在UNIX中该系统调用是:fork,fork会创建一个与父进程一模一样的副本,二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程) 2. 在windows中该系统调用是:CreateProcess,CreateProcess既处理进程的创建,也负责把正确的程序装入新进程。 关于创建子进程,UNIX和windows 1.相同的是:进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。 2.不同的是:在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是对于windows系统来说,从一开始父进程与子进程的地址空间就是不同的。
创建进程
进程的结束
1. 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)
2. 出错退出(自愿,python a.py中a.py不存在)
3. 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try...except...)
4. 被其他进程杀死(非自愿,如kill -9)
在python程序中的进程操作
之前我们已经了解了很多进程相关的理论知识,了解进程是什么应该不再困难了,刚刚我们已经了解了,运行中的程序就是一个进程。所有的进程都是通过它的父进程来创建的。因此,运行起来的python程序也是一个进程,那么我们也可以在程序中再创建进程。多个进程可以实现并发效果,也就是说,当我们的程序中存在多个进程的时候,在某些时候,就会让程序的执行速度变快。以我们之前所学的知识,并不能实现创建进程这个功能,所以我们就需要借助python中强大的模块。
获取当前进程的ID号:
用os模块的os.getpid() 来获取当前进程的id号
import os
print(os.getpid()) # 这个是获取你当前的进程的id
multiprocess模块:
对于进程我们可以使用multiprocess来进行管理
仔细说来,multiprocess不是一个模块而是python中一个操作、管理进程的包。 之所以叫multi是取自multiple的多功能的意思,在这个包中几乎包含了和进程有关的所有子模块。由于提供的子模块非常多,为了方便大家归类记忆,我将这部分大致分为四个部分:创建进程部分,进程同步部分,进程池部分,进程之间数据共享。
在windows情况下创建一个进程:
from multiprocess import Process
def process1():
print('process1:',os.getpid())
time.sleep(10)
if __name__ == '__main__':
print(os.getpid()) # 打印当前的进程
p = Process(target = process1) # 创建一个子进程
p.strat() # 开始
我们上面是创建没有参数的进程 下面可以创建有参数的子进程:
import os
from multiprocessing import Process
def process1(num,age):
print('process1:',os.getpid())
print('n:',num,age) # 打印参数
if __name__ == '__main__':
print(os.getpid())
p =Process(target = process1,args= [30,50]) # agrs是对进程进行传递参数
p.start()
创建有参数的子进程
进程与子进程:
当前进程是os.getpid() 父进程是os.getppid() 来获取的
import os
import time
from multiprocessing import Process
def func():
print(os.getpid(),os.getppid()) #os.getpid()是获取你的子进程,os.getppid()是获取你子进程的父进程就是你当前的进程 if __name__ =='__main__':
print(os.getpid(),os.getppid()) #os.getpid()是获取当前进程的id ,os.getppid()是获取你当前进程的父进程就是pycharm的进程id
# Process(target = func).start()
p = Process(target = func) # 建立一个新子进程
p.start()
print('*'*20)
time.sleep(3)
print('*'*40)
主进程默认会等待子进程执行完毕之后才结束
主进程和子进程之间的代码是异步的
为什么主进程要等待子进程结束 回收一些子进程的资源
开启一个进程是有时间开销的 :操作系统响应开启进程指令,给这个进程分配必要的资源
我们也可在进程中来判断这个进程是否已经执行完毕的用join来判断
使用process模块创建进程:
在一个python进程中开启子进程,start方法和并发效果。
import time
from multiprocessing import Process def f(name):
print('hello', name)
time.sleep(1)
print('我是子进程') if __name__ == '__main__':
p = Process(target=f, args=('bob',))
p.start()
#p.join()
print('我是父进程')
在python中启动的第一个子进程
import os
from multiprocessing import Process def f(x):
print('子进程id :',os.getpid(),'父进程id :',os.getppid())
return x*x if __name__ == '__main__':
print('主进程id :', os.getpid())
p_lst = []
for i in range(5):
p = Process(target=f, args=(i,))
p.start()
查看主进程和子进程的进程号
进阶,多个进程同时运行(注意,子进程的执行顺序不是根据启动顺序决定的)
import time
from multiprocessing import Process def f(name):
print('hello', name)
time.sleep(1) if __name__ == '__main__':
p_lst = []
for i in range(5):
p = Process(target=f, args=('bob',))
p.start()
p_lst.append(p)
多个进程同时执行
import time
from multiprocessing import Process def f(name):
print('hello', name)
time.sleep(1) if __name__ == '__main__':
p_lst = []
for i in range(5):
p = Process(target=f, args=('bob',))
p.start()
p_lst.append(p)
p.join()
# [p.join() for p in p_lst]
print('父进程在执行')
多个进程同时运行,再谈join方法(1)
import time
from multiprocessing import Process def f(name):
print('hello', name)
time.sleep(1) if __name__ == '__main__':
p_lst = []
for i in range(5):
p = Process(target=f, args=('bob',))
p.start()
p_lst.append(p)
# [p.join() for p in p_lst]
print('父进程在执行')
多个进程同时运行,再谈join方法(2)
除了上面这些开启进程的方法,还有一种以继承Process类的形式开启进程的方式
import os
from multiprocessing import Process
class MyProcess(Process):
def __init__(self,name):
super().__init__()
self.name = name
def run(self):
print(os.getpid())
print('%s 正在和女生聊天'%self.name ) if __name__ == '__main__': p1 = MyProcess('wusir')
p2 = MyProcess('元昊')
p3 = MyProcess('axel')
p1.start()
p2.start()
p3.start()
p1.join()
p2.join()
p3.join()
print('主线程')
通过继承Process类开启进程
进程之间的数据隔离问题
from multiprocessing import Process def work():
global n
n=0
print('子进程内: ',n) if __name__ == '__main__':
n = 100
p=Process(target=work)
p.start()
print('主进程内: ',n)
进程之间的数据隔离问题
同步控制:
import os
from multiprocessing import Process
def func(sex):
print('process1:',os.getpid(),os.getppid())
sex =eval(sex)
with open('file','w')as f:
f.write(str(sex)) # 把你的信息 写入一个文件内
if __name__ =='__main__':
print(os.getpid(),os.getppid())
p = Process(target = func,args = ['3*5'])
p.start()
ret = 4*5
p.join() # 用来检测你的上面的进程是否执行完 如果没有执行完就把上面的进程执行完 才能执行下面的
with open('file') as f:
result = f.read()
ret += int(result)
print(ret)
同步控制
join是用来控制你的进程的执行的 是为了让你上面的进程执行完毕才能执行下面的进程的,为了防止你上面的进程海没有结束就执行了下面的代码的
开启多个进程:
import os
import time
from multiprocessing import Process
def process(n):
print(os.getpid(),os.getppid())
time.sleep(3)
print(n)
if __name__ == '__main__':
p_lst = []
for i in range(10):
p = Process(target = process,args = [i,])
p.start()
p_lst.append(p)
for p in p_lst:p.join() # 检测p是否结束 如果没有结束就阻塞直到结束 如果已经结束了就不阻塞
print('求和')
开启多个进程
import os
from multiprocessing import Process
class Myprocess(Process):
def __init__(self,*args):
super().__init__()
self.args = args
def run(self):
print(os.getpid(),self.name,self.pid)
for name in self.args:
print('%s和女主播聊天'%name) if __name__ == '__main__':
print(os.getpid())
p = Myprocess('yuan','wusir')
p.start() # 在执行start的时候,会帮我们主动执行run方法中的内容
开启进程的第二个方法
进程中的数据隔离:
from multiprocessing import Process
n = 100
def func():
global n
n += 1
print('son : ',n) if __name__ == '__main__':
p = Process(target=func)
p.start()
p.join()
print(n)
进程中的数据隔离
守护进程:
import time
from multiprocessing import Process def func():
print('son start')
while True:
time.sleep(1)
print('son') def func2():
print('start :in func2')
time.sleep(5)
print('end : in func2')
if __name__ == '__main__':
p = Process(target=func)
# 在一个进程开启之前可以设置它为一个守护进程
p.daemon = True
p.start()
Process(target=func2).start()
time.sleep(2)
print('在主进程中')
守护进程
分析:
主进程的代码 大概在2s多的时候就结束了
p2子进程实在5s多的时候结束
主进程结束
p是在什么时候结束的?
p是在主进程的代码执行完毕之后就结束了 主进程会等待子进程的结束而结束
守护进程的意义:
子进程会随着主进程代码的执行结束而结束
注意:守护进程不会关系主进程什么时候结束,我只关心主进程中的代码什么时候结束
守护进程的作用:
守护主进程,程序报活
主进程开启的时候 建立一个守护进程
守护进程只负责每隔1分钟 就给检测程序发一条消息
进程结束的其他方法:
import time
from multiprocessing import Process def func():
print('wahaha')
time.sleep(20)
print('wahaha end')
if __name__ == '__main__':
p = Process(target=func)
p.start()
print(p.is_alive())
time.sleep(1)
p.terminate() # 在主进程中结束一个子进程
print(p.is_alive())
time.sleep(0.5)
print(p.is_alive())
# print(p.pid)
# print(p.name)
进程结束的其他方法
上面是把程序异步,让多个任务可以在几个进程中并发处理
进程同步(multiprocess.Lock、multiprocess.Semaphore、
multiprocess.Event)
锁 —— multiprocess.Lock
锁是控制一段代码同一时间一段代码只能被一个锁给执行,锁就是好比一个屋子你同时有好几个进程想要进去但是呢,这个时候这些进程就开始抢跑,然后第一个拿到锁的人进屋子后就把屋子给锁住,当他进去之后把屋子给上锁 然后再里面做不可人知的事,他在做的过程因为已经锁住门了,其他的进程是进不去的,只能等他结束才能进去
下面是对多个进程进行上锁的实现:
import os
import time
import random
from multiprocessing import Process
from multiprocessing import Lock
def work(n,lock):
lock.acquire() # 上锁
print('{}{}is running'.format(n,os.getpid())) # 把进程id打印出来
time.sleep(random.random())
print('{}{}is running'.format(n,os.getpid()))
lock.release() # 上锁结束 释放掉你上的锁 if __name__ == '__main__':
lock = Lock() # 实例化一个锁
for i in range(10):
p = Process(target = work, args = [i,lock]) # 把你的进程数和锁传递进函数中就是你的进程中
p.start()
上锁的时候lock.acquire()是进项上锁 lock.release()是对你的锁进行释放代表抢先进去的进程已经完成了 可以让下一个进去了
lock.acquire()
.......
......
lock.release()
这个过程是上锁到结束的过程
release就是还锁,不还锁就没办法进行了
# 由并发变成了串行,牺牲了运行效率,但避免了竞争
import os
import time
import random
from multiprocessing import Process,Lock def work(lock,n):
lock.acquire()
print('%s: %s is running' % (n, os.getpid()))
time.sleep(random.random())
print('%s: %s is done' % (n, os.getpid()))
lock.release()
if __name__ == '__main__':
lock=Lock()
for i in range(3):
p=Process(target=work,args=(lock,i))
p.start()
使用锁维护执行顺序
上面这种情况虽然使用加锁的形式实现了顺序的执行,但是程序又重新变成串行了,这样确实会浪费了时间,却保证了数据的安全。
接下来,我们以模拟抢票为例,来看看数据安全的重要性。
#文件db的内容为:{"count":1}
#注意一定要用双引号,不然json无法识别
#并发运行,效率高,但竞争写同一文件,数据写入错乱
from multiprocessing import Process,Lock
import time,json,random
def search():
dic=json.load(open('db'))
print('\033[43m剩余票数%s\033[0m' %dic['count']) def get():
dic=json.load(open('db'))
time.sleep(0.1) #模拟读数据的网络延迟
if dic['count'] >0:
dic['count']-=1
time.sleep(0.2) #模拟写数据的网络延迟
json.dump(dic,open('db','w'))
print('\033[43m购票成功\033[0m') def task():
search()
get() if __name__ == '__main__':
for i in range(100): #模拟并发100个客户端抢票
p=Process(target=task)
p.start()
多进程同时抢购余票
#文件db的内容为:{"count":5}
#注意一定要用双引号,不然json无法识别
#并发运行,效率高,但竞争写同一文件,数据写入错乱
from multiprocessing import Process,Lock
import time,json,random
def search():
dic=json.load(open('db'))
print('\033[43m剩余票数%s\033[0m' %dic['count']) def get():
dic=json.load(open('db'))
time.sleep(random.random()) #模拟读数据的网络延迟
if dic['count'] >0:
dic['count']-=1
time.sleep(random.random()) #模拟写数据的网络延迟
json.dump(dic,open('db','w'))
print('\033[32m购票成功\033[0m')
else:
print('\033[31m购票失败\033[0m') def task(lock):
search()
lock.acquire()
get()
lock.release() if __name__ == '__main__':
lock = Lock()
for i in range(100): #模拟并发100个客户端抢票
p=Process(target=task,args=(lock,))
p.start()
使用锁来保证数据安全
#加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
虽然可以用文件共享数据实现进程间通信,但问题是:
1.效率低(共享数据基于文件,而文件是硬盘上的数据)
2.需要自己加锁处理 #因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。
队列和管道都是将数据存放于内存中
队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,
我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。
信号量 —— multiprocess.Semaphore(了解)
互斥锁同时只允许一个线程更改数据,而信号量Semaphore是同时允许一定数量的线程更改数据 。
假设商场里有4个迷你唱吧,所以同时可以进去4个人,如果来了第五个人就要在外面等待,等到有人出来才能再进去玩。
实现:
信号量同步基于内部计数器,每调用一次acquire(),计数器减1;每调用一次release(),计数器加1.当计数器为0时,acquire()调用被阻塞。这是迪科斯彻(Dijkstra)信号量概念P()和V()的Python实现。信号量同步机制适用于访问像服务器这样的有限资源。
信号量与进程池的概念很像,但是要区分开,信号量涉及到加锁的概
信号量的介绍
from multiprocessing import Process,Semaphore
import time,random def go_ktv(sem,user):
sem.acquire()
print('%s 占到一间ktv小屋' %user)
time.sleep(random.randint(0,3)) #模拟每个人在ktv中待的时间不同
sem.release() if __name__ == '__main__':
sem=Semaphore(4)
p_l=[]
for i in range(13):
p=Process(target=go_ktv,args=(sem,'user%s' %i,))
p.start()
p_l.append(p) for i in p_l:
i.join()
print('============》')
例子
其实信号量就是你的锁但是是控制了锁的钥匙
事件 —— multiprocess.Event(了解)
python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。 事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。 clear:将“Flag”设置为False
set:将“Flag”设置为True
时间介绍
from multiprocessing import Process, Event
import time, random def car(e, n):
while True:
if not e.is_set(): # 进程刚开启,is_set()的值是Flase,模拟信号灯为红色
print('\033[31m红灯亮\033[0m,car%s等着' % n)
e.wait() # 阻塞,等待is_set()的值变成True,模拟信号灯为绿色
print('\033[32m车%s 看见绿灯亮了\033[0m' % n)
time.sleep(random.randint(3, 6))
if not e.is_set(): #如果is_set()的值是Flase,也就是红灯,仍然回到while语句开始
continue
print('车开远了,car', n)
break def police_car(e, n):
while True:
if not e.is_set():# 进程刚开启,is_set()的值是Flase,模拟信号灯为红色
print('\033[31m红灯亮\033[0m,car%s等着' % n)
e.wait(0.1) # 阻塞,等待设置等待时间,等待0.1s之后没有等到绿灯就闯红灯走了
if not e.is_set():
print('\033[33m红灯,警车先走\033[0m,car %s' % n)
else:
print('\033[33;46m绿灯,警车走\033[0m,car %s' % n)
break def traffic_lights(e, inverval):
while True:
time.sleep(inverval)
if e.is_set():
print('######', e.is_set())
e.clear() # ---->将is_set()的值设置为False
else:
e.set() # ---->将is_set()的值设置为True
print('***********',e.is_set()) if __name__ == '__main__':
e = Event()
for i in range(10):
p=Process(target=car,args=(e,i,)) # 创建是个进程控制10辆车
p.start() for i in range(5):
p = Process(target=police_car, args=(e, i,)) # 创建5个进程控制5辆警车
p.start()
t = Process(target=traffic_lights, args=(e, 10)) # 创建一个进程控制红绿灯
t.start() print('============》') # 状态
# 子进程 如何 受到状态的影响?
# wait() 的方法 等待 ---> 信号
# 发送信号:通过事件来发送信号
# True set 把信号设置为True
# False clear 把信号设置位False # 红绿灯 :
# 车 进程 wait() 等红灯
# 根据状态变化 wait遇到True信号,就非阻塞
# 遇到False信号,就阻塞
# 交通灯 进程 红灯 --> False
# 绿灯 --> True # 事件
# wait的方法 根据一个状态来决定自己是否要阻塞
# 状态相关的方法
# set 将状态改为T
# clear 将状态改为F
# is_set 判断当前的状态是否为T # from multiprocessing import Event # # 创建一个事件的对象
# e = Event()
# print(e.is_set()) # 在事件的创世之初,状态为False
# e.set()
# e.wait()
# print(e.is_set())
# e.clear()
# print(e.is_set())
# e.wait() import time
import random
from multiprocessing import Process,Event def car(i,e): # 感知状态的变化
if not e.is_set(): # 当前这个事件的状态如果是False
print('car%s正在等待'%i) # 这辆车正在等待通过路口
e.wait() # 阻塞 直到有一个e.set行为 # 等红灯
print('car%s通过路口'%i) def traffic_light(e): # 修改事件的状态
print('\033[1;31m红灯亮\033[0m')
# 事件在创立之初的状态是False,相当于我程序中的红灯
time.sleep(2) # 红灯亮2s
while True:
if not e.is_set(): # False
print('\033[1;32m绿灯亮\033[0m')
e.set()
elif e.is_set():
print('\033[1;31m红灯亮\033[0m')
e.clear()
time.sleep(2) if __name__ == '__main__':
e = Event()
Process(target=traffic_light,args=[e,]).start()
for i in range(50):
time.sleep(random.randrange(0,5,2))
Process(target=car,args=[i,e]).start()
红绿灯实例
wait()的方法根据一个状态来决定自己是否要阻塞,
状态相关的方法:
set 将状态改为T
clear将状态改为F
is_set 判断当前的状态是否为T
事件的初始状态是False
进程间通信——队列和管道(multiprocess.Queue、multiprocess.Pipe)
生产者消费者模型
消费者 消费数据 吃包子
生产者 产生数据的人 做包子
供销矛盾
10 供不应求
增加做包子的人
同步 :做一个包子 卖一包子 吃一包子
做包子慢 吃包子快
生产数据 买淘宝 ---
消费数据 阿里 --- 非常高 必须要快速把用户生产的数据消费完
python之路——进程的更多相关文章
- python之路-进程
博客园 首页 新随笔 联系 管理 订阅 随笔- 31 文章- 72 评论- 115 python之路——进程 阅读目录 理论知识 操作系统背景知识 什么是进程 进程调度 进程的并发与并行 ...
- python之路----进程(一)
一.理论知识1.操作系统发展简介 1.没有操作系统 —— 穿孔卡片 2.批处理系统 —— 串行 ,速度块 联机批处理 读磁带的时候速度快 脱机批处理 读磁带和cpu工作并发 3.多道程序系统 —— 并 ...
- Python之路,进程、线程、协程篇
本节内容 进程.与线程区别 cpu运行原理 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者 ...
- python之路----进程三
IPC--PIPE管道 #创建管道的类: Pipe([duplex]):在进程之间创建一条管道,并返回元组(conn1,conn2),其中conn1,conn2表示管道两端的连接对象,强调一点:必须在 ...
- python之路----进程二
守护进程 会随着主进程的结束而结束. 主进程创建守护进程 其一:守护进程会在主进程代码执行结束后就终止 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic ...
- python之路--进程内容补充
一. 进程的其他方法 进程id, 进程名字, 查看进程是否活着(is_alive()), terminate()发送结束进程的信号 import time import os from multipr ...
- Python之路【第七篇】:线程、进程和协程
Python之路[第七篇]:线程.进程和协程 Python线程 Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元. 1 2 3 4 5 6 7 8 9 10 11 12 1 ...
- Python之路【第十一篇】: 进程与线程
阅读目录 一. cpython并发编程之多进程1.1 multiprocessing模块介绍1.2 Process类的介绍1.3 Process类的使用1.4 进程间通信(IPC)方式一:队列1.5 ...
- python之路 目录
目录 python python_基础总结1 python由来 字符编码 注释 pyc文件 python变量 导入模块 获取用户输入 流程控制if while python 基础2 编码转换 pych ...
随机推荐
- 【Ubuntu】执行定时任务(cron)
1.打开定时任务配置文件 crontab -e 2.编写定时任务时间 命令和脚本例如: /3 * * * * /soft/config/test.sh 前5个字段为时间,后面的一个为命令 前5个含义为 ...
- shiro学习笔记_0700_整合ssm
现在最流行的框架就是ssm,学到最后,shiro在实际开发中,也就的整合框架.首先spring是少不了的,shiro也提供了和spring的整合包. 首先,新建maven项目: maven依赖: &l ...
- emacs26.1 ppa
sudo add-apt-repository ppa:kelleyk/emacssudo apt updatesudo apt install emacs26
- Struts1原理解析
1.浏览器发送http请求->web服务器. 2.web服务器将 请求进行解析. 3.web服务器解析后将请求转发给ActionServelet(总队长). 3.查询struts-config. ...
- 定时删除elasticsearch索引
从去年搭建了日志系统后,就没有去管它了,最近发现大半年各种日志的index也蛮多的,就想着写个脚本定时清理一下,把一些太久的日志清理掉. 脚本思路:通过获取index的尾部时间与我们设定的过期时间进行 ...
- 逻辑备份,mysqldump,SELECT…INTO OUTFILE,恢复
逻辑备份 mysqldump mysqldump备份工具最初由Igor Romanenko编写完成,通常用来完成转存(dump)数据库的备份以及不同数据库之间的移植,例如从低版本的MySQL数据库升级 ...
- PHP将字符串写入txt文件
function writelog($str) { $open=fopen("e:\log.txt","a" ); fwrite($open,$str); fc ...
- 深入理解java集合框架之---------HashTable集合
HashTable是什么 HashTable是基于哈希表的Map接口的同步实现 HashTable中元素的key是唯一的,value值可重复 HashTable中元素的key和value不允许为nul ...
- [中英对照]The Art Of Reporting Bugs | 报bug的艺术
前言:因为最近要给兄弟Team分享一下如何有效地报告bug, 故多做一做功课.下面给出一篇博客的中英文对照翻译. The Art Of Reporting Bugs | 报bug的艺术 My init ...
- 从mdb到crash
本文面向使用过Solaris的mdb但是没有使用过Linux的crash的同学.比如说我自己,mdb用了很多年,现在全面转向Linux平台,于是很好奇Linux有没有类似的工具.熟悉Solaris的同 ...