所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。
例子
阻塞与非阻塞
阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的
继续上面的那个例子,不论是排队还是使用号码等待通知,如果在这个等待的过程中,等待者除了等待消息通知之外不能做其它的事情,那么该机制就是阻塞的,表现在程序中,也就是该程序一直阻塞在该函数调用处不能继续往下执行。
相反,有的人喜欢在银行办理这些业务的时候一边打打电话发发短信一边等待,这样的状态就是非阻塞的,因为他(等待者)没有阻塞在这个消息通知上,而是一边做自己的事情一边等待。
注意:同步非阻塞形式实际上是效率低下的,想象一下你一边打着电话一边还需要抬头看到底队伍排到你了没有。如果把打电话和观察排队的位置看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的;而异步非阻塞形式却没有这样的问题,因为打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换。
例子
同步/异步与阻塞/非阻塞
同步阻塞形式
效率最低。拿上面的例子来说,就是你专心排队,什么别的事都不做。
异步阻塞形式
如果在银行等待办理业务的人采用的是异步的方式去等待消息被触发(通知),也就是领了一张小纸条,假如在这段时间里他不能离开银行做其它的事情,那么很显然,这个人被阻塞在了这个等待的操作上面;
异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。
同步非阻塞形式
实际上是效率低下的。
想象一下你一边打着电话一边还需要抬头看到底队伍排到你了没有,如果把打电话和观察排队的位置看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的。
异步非阻塞形式
效率更高,
因为打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换。
比如说,这个人突然发觉自己烟瘾犯了,需要出去抽根烟,于是他告诉大堂经理说,排到我这个号码的时候麻烦到外面通知我一下,那么他就没有被阻塞在这个等待的操作上面,自然这个就是异步+非阻塞的方式了。
很多人会把同步和阻塞混淆,是因为很多时候同步操作会以阻塞的形式表现出来,同样的,很多人也会把异步和非阻塞混淆,因为异步操作一般都不会在真正的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和windows1.相同的是:进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。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号
importosprint(os.getpid()) #这个是获取你当前的进程的id
multiprocess模块:
对于进程我们可以使用multiprocess来进行管理
仔细说来,multiprocess不是一个模块而是python中一个操作、管理进程的包。 之所以叫multi是取自multiple的多功能的意思,在这个包中几乎包含了和进程有关的所有子模块。由于提供的子模块非常多,为了方便大家归类记忆,我将这部分大致分为四个部分:创建进程部分,进程同步部分,进程池部分,进程之间数据共享。
在windows情况下创建一个进程:
from multiprocess importProcessdefprocess1():print('process1:',os.getpid())
time.sleep(10)if __name__ == '__main__':print(os.getpid()) #打印当前的进程
p = Process(target = process1) #创建一个子进程
p.strat() #开始
我们上面是创建没有参数的进程 下面可以创建有参数的子进程:
importosfrom multiprocessing importProcessdefprocess1(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() 来获取的
importosimporttimefrom multiprocessing importProcessdeffunc():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)
View Code
主进程默认会等待子进程执行完毕之后才结束
主进程和子进程之间的代码是异步的
为什么主进程要等待子进程结束 回收一些子进程的资源
开启一个进程是有时间开销的 :操作系统响应开启进程指令,给这个进程分配必要的资源
我们也可在进程中来判断这个进程是否已经执行完毕的用join来判断
使用process模块创建进程:
在一个python进程中开启子进程,start方法和并发效果。
importtimefrom multiprocessing importProcessdeff(name):print('hello', name)
time.sleep(1)print('我是子进程')if __name__ == '__main__':
p= Process(target=f, args=('bob',))
p.start()#p.join()
print('我是父进程')
在python中启动的第一个子进程
importosfrom multiprocessing importProcessdeff(x):print('子进程id :',os.getpid(),'父进程id :',os.getppid())return x*xif __name__ == '__main__':print('主进程id :', os.getpid())
p_lst=[]for i in range(5):
p= Process(target=f, args=(i,))
p.start()
查看主进程和子进程的进程号
进阶,多个进程同时运行(注意,子进程的执行顺序不是根据启动顺序决定的)
importtimefrom multiprocessing importProcessdeff(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)
多个进程同时执行
importtimefrom multiprocessing importProcessdeff(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)
importtimefrom multiprocessing importProcessdeff(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类的形式开启进程的方式
importosfrom multiprocessing importProcessclassMyProcess(Process):def __init__(self,name):
super().__init__()
self.name=namedefrun(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 importProcessdefwork():globaln
n=0print('子进程内:',n)if __name__ == '__main__':
n= 100p=Process(target=work)
p.start()print('主进程内:',n)
进程之间的数据隔离问题
同步控制:
importosfrom multiprocessing importProcessdeffunc(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*5p.join()#用来检测你的上面的进程是否执行完 如果没有执行完就把上面的进程执行完 才能执行下面的
with open('file') as f:
result=f.read()
ret+=int(result)print(ret)
同步控制
join是用来控制你的进程的执行的 是为了让你上面的进程执行完毕才能执行下面的进程的,为了防止你上面的进程海没有结束就执行了下面的代码的
开启多个进程:
importosimporttimefrom multiprocessing importProcessdefprocess(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('求和')
开启多个进程
importosfrom multiprocessing importProcessclassMyprocess(Process):def __init__(self,*args):
super().__init__()
self.args=argsdefrun(self):print(os.getpid(),self.name,self.pid)for name inself.args:print('%s和女主播聊天'%name)if __name__ == '__main__':print(os.getpid())
p= Myprocess('yuan','wusir')
p.start()#在执行start的时候,会帮我们主动执行run方法中的内容
开启进程的第二个方法
进程中的数据隔离:
from multiprocessing importProcess
n= 100
deffunc():globaln
n+= 1
print('son :',n)if __name__ == '__main__':
p= Process(target=func)
p.start()
p.join()print(n)
进程中的数据隔离
守护进程:
importtimefrom multiprocessing importProcessdeffunc():print('son start')whileTrue:
time.sleep(1)print('son')deffunc2():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分钟 就给检测程序发一条消息
进程结束的其他方法:
importtimefrom multiprocessing importProcessdeffunc():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就是还锁,不还锁就没办法进行了
#由并发变成了串行,牺牲了运行效率,但避免了竞争
importosimporttimeimportrandomfrom multiprocessing importProcess,Lockdefwork(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 importProcess,Lockimporttime,json,randomdefsearch():
dic=json.load(open('db'))print('\033[43m剩余票数%s\033[0m' %dic['count'])defget():
dic=json.load(open('db'))
time.sleep(0.1) #模拟读数据的网络延迟
if dic['count'] >0:
dic['count']-=1time.sleep(0.2) #模拟写数据的网络延迟
json.dump(dic,open('db','w'))print('\033[43m购票成功\033[0m')deftask():
search()
get()if __name__ == '__main__':for i in range(100): #模拟并发100个客户端抢票
p=Process(target=task)
p.start()
多进程同时抢购余票
#文件db的内容为:{"count":5}#注意一定要用双引号,不然json无法识别#并发运行,效率高,但竞争写同一文件,数据写入错乱
from multiprocessing importProcess,Lockimporttime,json,randomdefsearch():
dic=json.load(open('db'))print('\033[43m剩余票数%s\033[0m' %dic['count'])defget():
dic=json.load(open('db'))
time.sleep(random.random())#模拟读数据的网络延迟
if dic['count'] >0:
dic['count']-=1time.sleep(random.random())#模拟写数据的网络延迟
json.dump(dic,open('db','w'))print('\033[32m购票成功\033[0m')else:print('\033[31m购票失败\033[0m')deftask(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 importProcess,Semaphoreimporttime,randomdefgo_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 inp_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 importProcess, Eventimporttime, randomdefcar(e, n):whileTrue: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
defpolice_car(e, n):whileTrue:if not e.is_set():#进程刚开启,is_set()的值是Flase,模拟信号灯为红色
print('\033[31m红灯亮\033[0m,car%s等着' %n)
e.wait(0.1) #阻塞,等待设置等待时间,等待0.1s之后没有等到绿灯就闯红灯走了
if note.is_set():print('\033[33m红灯,警车先走\033[0m,car %s' %n)else:print('\033[33;46m绿灯,警车走\033[0m,car %s' %n)break
deftraffic_lights(e, inverval):whileTrue:
time.sleep(inverval)ife.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()
importtimeimportrandomfrom multiprocessing importProcess,Eventdef 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
whileTrue:if not e.is_set(): #False
print('\033[1;32m绿灯亮\033[0m')
e.set()elife.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 供不应求
增加做包子的人
同步 :做一个包子 卖一包子 吃一包子
做包子慢 吃包子快
生产数据 买淘宝 ---
消费数据 阿里 --- 非常高 必须要快速把用户生产的数据消费完