python开发基础篇:五:多进程
1:操作系统发展史:参考文档:https://www.cnblogs.com/Eva-J/articles/8253521.html
I/O:input/output 输入/输出
文件的read读取也是一种输入的行为
文件的write是一种输出行为
网络编程sk.recv是一种输入的行为
网络编程sk.send是输出的过程
time.sleep也是一种i/o,只是这个i/o什么都不干
数据库的连接以及数据库的存储读取消息也都是i/o
其实对于cpu来讲让cpu等待就是一种I/O,输入输出的过程都是让cpu等待数据的过程,没有数据就会让cpu陷入等待,这些都算i/o
单道程序工作示意图:
多道程序工作示意图:
多道程序设计:
a程序I/O阻塞切换到b程序运行,a程序的运行状态需要保存记录,这个保存记录就是操作系统干的
操作系统负责执行程序,执行到a程序中间位置发现了I/O了,操作系统帮忙做切换切换到另外程序
a程序这时候的运行状态由操作系统帮助操作完成保存,这些数据有的存储到寄存器里---a程序的运行状态一定是被保存了才能保证在两个程序之间来回切换
单处理机系统中多道程序运行时的特点:
(1)多道:计算机内存中同时存放几道相互独立的程序;起来的多个程序之间内存是隔开的,操作让内存实现界限让多个程序之间内存独立
(2)宏观上并行:同时进入系统的几道程序都处于运行过程中,即它们先后开始了各自的运行,但都未运行完毕;
(3)微观上串行:实际上,各道程序轮流地用CPU,并交替运行
串行:一个任务结束再执行另一个程序
并行:多个任务一起运行
操作系统:
管理多个程序的使用,给这些程序分配cpu等硬件资源,谁能使用cpu等硬件资源cpu来调配
多道程序系统的出现,标志着操作系统渐趋成熟的阶段,先后出现了作业调度管理、处理机管理、存储器管理、外部设备管理、文件系统管理等功能
空间隔离:
内存空间的隔离,让数据更加安全、稳定(每个运行的任务都有它自己的独立的内存空间)
时空复用:
遇到IO操作就切换程序,使得cpu的利用率提高了,计算机的工作效率也随之提高
分时系统: 将单位时间分成一段段的时间片,按时间片轮流把处理机分配给各联机作业使用
分时系统让这个程序每一个时间片切一次切换的过程会耗费时间,所以分时系统的出现反而降低了处理任务的效率
但是能够让多个程序看起来好像一起在执行一样
(比如一边看着电源,一边聊qq,这是两个程序,但是计算机是轮转处理这两个任务的,时间片长度很短很短,让人根本感知不到任务是断的轮询运行的,看起来连续的一样)---基于单核的机器
现在的电脑都算4核8核的,一个cpu同一时间只能做一件事情,如果时间分很小所有的任务排队轮流执行,
比如一个cpu同时处理20个程序,20个程序让我们用起来像是一起执行的,如果16核32核,就能20*16这么多的程序同时一起运行
现在广泛使用的都算分时系统
实时系统:
实时系统在一个特定的应用中常作为一种控制设备来使用,
实时系统只为了响应一件事情,实时系统一个cpu一直等一个事情,给一个指令cpu马上就响应你,就是要快,速度,比如战斗机,导弹,实时信息处理系统,更快的响应你的请求
实时系统同时能并发做的任务不多,有几个cpu就只能做几个事情,否则就会出问题
通用操作系统:
操作系统的三种基本类型:多道批处理系统、分时系统、实时系统
通用操作系统:
具有多种类型操作特征的操作系统。可以同时兼有多道批处理、分时、实时处理的功能,或其中两种以上的功能。
电脑不是单纯的分时系统,a程序和b程序分时,假设a遇到io操作了,a程序就不轮训了占用时间片了,a遇到io了就cpu让给b让他多执行一会 这个过程就是多道批处理+分时
又来了个c,但是任务里面有更重要的任务比如b,可以把b任务的优先级设高,这样的话可以分给b任务的时间片比别的任务更长,或者abc3个任务都准备就绪的状态下先调用b
---优先级概念有点类似实时系统概念
现在的windows,linux都算通用操作系统,不是单一的一直类型,具有多种类型的概念
分布式操作系统:
一个很大的程序或者任务通过各种手段(软件+硬件方面)把这个大任务拆分成多个子任务,
把子任务分布到几台机器上面然后让这几台机器分别完成小任务,然后每一台机器最终结果返回,然后完成最后想要的结果
操作系统的功能: 1:封装了对硬件的操作过程,给应用程序提供好用的接口 2:对多个作业进行调度管理来分配硬件资源
2: 进程参考文档:https://www.cnblogs.com/Eva-J/articles/8253549.html
进程:
只有在运行当中的程序才叫做进程,每运行起来一个程序就启动了一个进程
操作系统和进程的关系:
操作系统是在对进程进行调度,运行起来的程序在操作系统当中产生一个任务,这个任务就是进程
为什么引入进程的概念:
一个程序当他运行起来之后就产生一个进程,这个程序有属于自己的内存和数据,一个进程和另外一个进程之间的数据和内存空间等资源都是隔离的
这就是为什么引入进程的概念,为了隔离资源
进程:
进程是操作系统中资源分配的最小单位(某块资源内存,cpu,其他等资源分配给进程)
进程是怎么被调度的:
1:先来先服务算法,FCFS:frist come frist servred
2:短作业优先算法,谁能快速执行完谁优先(但是有些时候不知道一个程序运行多长时间,比如qq音乐)
3:时间片轮转算法(雨露均沾比上面2个合理)
4:多级反馈算法
进程process多个
1 = [p2, p3]
创建几个队列,进程进来放到队列1里,cpu时间片到了的时候就拿p1过来先来执行
如果执行了一个时间片还没有执行完p1,就把p1放到第二个队列2里,执行过他一次就让他降一级
cpu优先执行队列1里的,任务执行一个时间片执行不玩就放到队列2里,队列2里的再执行一个时间片执行不完放到队列3里
一级级逐级往下走,先进来的任务越优先处理,不管任务要执行多长时间,第一次进来的时候都优先处理你先给你个时间片,cpu优先处理队列1的任务处理完再处理队列2里的任务,以此类推
当进程轮转几个时间片结束了之后这个进程就结束了任务
当队列3得任务正在执行的时候突然队列1来了个新的进程,就放下队列3里得任务去执行队列1得任务
2 = [p1]
3 = []
现在的操作系统当中多多少少都用到了多级反馈算法的概念,只不过有些微弱的差别,可以人为调整进程的优先程度
进程都是操作系统在调度,跟我们没有关系,了解就行
现在的操作系统当中综合了这些所有的算法,然后还做了一些改进,linux和windows
进程的并行和并发:
并行 : 并行是指两者同时执行,比如赛跑,两个人都在不停的往前跑;(资源够用,比如三个线程,四核的CPU,单核不可能实现并行,需要多核,假设4核最多4个程序同时运行)
并发 : 并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率。
区别:
并行是从微观上,也就是在一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器。
并发是从宏观上,在一个时间段上可以看出是同时执行的,比如一个服务器同时处理多个session。
同步,异步,阻塞,非阻塞
阻塞:recv,input,accept这些需要等,等着啥也不能干
非阻塞:不等,接着往下走
同步:调度之后还一点要等待结果或者等待调度完成(我叫李二狗去吃饭,李二狗在这里打飞机,我就站在旁边等着他打完飞机就去一起吃饭了,一定要等到他去吃饭这个结果我才能走)
我只能做一件事情
异步:我只管调度发指令,不等结果(我叫李二狗去吃饭,然后我走了,然后李二狗接受到信息干完自己的工作后就去吃饭,自己去执行吃饭这个任务)
我同时能做多件事情
异步:银行拿一个号就走了,去干其他事情,短信通知你号快到我了,这时候才回去取钱
同步:拿到一个号就等着,等到别人叫我号办完业务再走
所以异步效率比较高
阻塞和非阻塞:
不止表现在编程当中还影响进程的调度
进程三状态切换图
在了解其他概念之前,我们首先要了解进程的几个状态。在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪,运行和阻塞。
(1)就绪(Ready)状态 当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。 (2)执行/运行(Running)状态当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。 (3)阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。
引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。
执行程序先创建一个进程,创建完进程之后提交进程就进入就绪状态,就绪状态的进程等待cpu的时间片,等到进程调度我了之后时间片轮到我就运行代码(占用cpu的过程)
假设进程碰到其他事件请求比如input或者sleep,recv,accept这些事件请求之后就进入一个阻塞状态,进入阻塞状态之后进程需要把cpu让出来给别人用
当事件发生(进程的阻塞状态完成了阻塞的事情代码)不会立刻抢占cpu,而是进程又去排队等待cpu时间片又轮到我再去运行
阻塞很浪费时间:遇到阻塞需要让出cpu,且阻塞事件完成后再想使用cpu需要重新的排队
如果占用了一个cpu时间片没有遇到阻塞时间到了进程的任务还是没有完成,进程又回到就绪状态,等下一个时间片轮到这个进程再继续执行任务,直到执行完了之后这个进程就释放
同步/异步与阻塞/非阻塞:
同步阻塞形式:效率最低,拿上面的例子来说,就是你专心排队,什么别的事都不做
异步阻塞形式:需要排队等着取钱,但是可以在等着的时候做其他事情
在银行等待办理业务的人采用的是异步的方式去等待消息被触发(通知)
,
也就是领了一张小纸条,假如在这段时间里他不能离开银行做其它的事情,那么很显然,这个人被阻塞在了这个等待的操作上面;
异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。
如果能取钱就直接取钱了,不阻塞了,等着通知通知去取钱,这个过程只能阻塞着
即便有cpu处理这个任务,但是没有数据也无法处理,比如说
:input,等待输入的数据做接下来的事情,可以异步两条分支去做事,但是不给数据任然做不了事情
同步非阻塞形式:一边打着电话一边还需要抬头看到底队伍排到你了没有,如果把打电话和观察排队的位置看成是程序的两个操作的话,
这个程序需要在这两种不同的行为之间来回的切换
,效率可想而知是低下的
异步非阻塞形式:打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换
。
比如说,这个人突然发觉自己烟瘾犯了,需要出去抽根烟,于是他告诉大堂经理说,排到我这个号码的时候麻烦到外面通知我一下,
那么他就没有被阻塞在这个等待的操作上面,自然这个就是异步+非阻塞的方式了
很多人会把同步和阻塞混淆:是因为很多时候同步操作会以阻塞的形式表现出来
,同样的,很多人也会把异步和非阻塞混淆,因为异步操作一般都不会在真正的IO操作处被阻塞
我告诉李二狗去吃饭然后等着他,等着他的过程就阻塞住了,这个只是说我在这个地方恰好等着他阻塞了,我这个人在同一时间只能做一件事情这是同步
异步:去银行取钱,既能排队又能打电话
进程的状态:就绪,运行,阻塞
进程的创建与结束:
创建进程有很多方式---就是让一个程序运行起来这就是创建进程,
所有的进程都是被其他进程创建出来的,创建他的进程就是这个进程的父进程,(父进程,子进程)
进程的结束:
3:python中的进程
os.getpid():
当前运行这个进程的id,进程id,获取当前运行的python程序的进程id
os.getppid():
parent process id,获取当前运行的进程的父进程的id
python也是一个进程,所有的进程都是由进程创建的,我们可以在python当中创建进程来替我做事情(python代码来实现)
创建进程是操作系统完成的,我们只需要调度操作系统给我们提供的接口就行,
python中的multiprocessing,subprocess
multiprocessing对于所有进程操作的进一步封装的模块,用起来最便捷的
subprocess是multiprocessing的一个底层模块,multiprocessin用来一部分subprocess来实现的,同时使用这两个模块可能会冲突报错
多进程我们一般使用multiprocessing模块来实现,
multiprocessing是一个非常综合的处理进程的模块(涉及到进程的创建,同步控制,进程之间的通信,进程之间的数据共享)
multi:多元的综合的
process:进程
process:和创建进程相关的
from multiprocessing import Process import os def func(): print(123) print(os.getpid(), os.getppid()) # 15228 11320 if __name__ == '__main__': p = Process(target=func) # 创建一个进程对象,func交给进程对象 print(os.getpid()) # 13740 p.start() # 执行start才有了进程,开启进程,给操作系统发送指令我要创建进程 # 在同一个进程当中程序确实是从上到下依次执行的 # 上面的程序是异步程序:上面程序的运行和下面程序的运行没有关系------
process模块实现异步
同步和异步
import os def func(): print(123) print(os.getpid(), os.getppid()) # 15228 11320 if __name__ == '__main__': print("我要去银行取钱") func() print("我取完钱了") 上面过程是同步的过程:func假设是去取钱的方法,我要去取钱,等着钱取完了,然后取完钱了 from multiprocessing import Process import os def func(): print(123) print(os.getpid(), os.getppid()) # 15228 11320 if __name__ == '__main__': p = Process(target=func) print("我要去银行取钱") p.start() print("我取完钱了") # 如果起一个进程来完成取钱这个过程,我就这去做我自己的事情 # 我接着做我的事情,但是我想去取钱,派一个人去帮我去取钱,指令发送给别的人 # 别人什么时候取钱,取多少钱我都管不了了,我就自己去干自己的事情了-----异步过程
阻塞和非阻塞 from multiprocessing import Process import os import time def func(): print(123) print(os.getpid(), os.getppid()) # 15228 11320 if __name__ == '__main__': p = Process(target=func) print("我要去银行取钱") p.start() time.sleep(10) # 主进程这里阻塞,和子进程没有关系 这个叫异步阻塞 # 异步指的是我和我的子进程异步,我的阻塞不影响子进程的阻塞----异步阻塞 print("我取完钱了") # 这就是异步阻塞 import os import time def func(): print(123) print(os.getpid(), os.getppid()) # 15228 11320 if __name__ == '__main__': func() time.sleep(10) print("我取完钱了") # 同步的程序,取完钱了又在这里等
# 这就是同步阻塞
from multiprocessing import Process import os def func(): print(123) print(f"子进程:{os.getpid()}") print(f"父进程:{os.getppid()}") if __name__ == '__main__': p = Process(target=func) print(f"主进程{os.getpid()}") p.start() print(f"主进程{os.getpid()}")
子进程是由python主进程开启的,所以子进程的父进程是python主进程,主进程的阻塞不影响子进程的运行
两个进程,第一个进程正常走,第二个进程阻塞停了一会再正常走,他两之间互不干扰就形成异步,各自运行各自的
主进程和子进程是异步的了,不需要子进程执行完了之后主进程里面的代码才执行,两
# join()函数的使用 from multiprocessing import Process
import time def func(money):
time.sleep(3) print(f"取钱:{money}") if __name__ == '__main__': p = Process(target=func, args=(100, )) p.start()
print("去取钱") p.join()
print("取完钱了") print("花钱") 打印: 取钱:100 取完钱了
主进程等着上面的子进程取钱代码执行完了之后才执行下面的花钱代码,取完钱之后才能花钱,使用join方法
正常情况下一个程序的执行结束:
子进程启动之后和主进程没有干连,主进程可以正常执行代码,在这个过程当中这两是异步的,
主进程结束之前需要等待所有子进程全部结束之后才会结束主进程,主进程结束整个程序也结束了
这里需要等待子进程执行完之后才去打印"花钱" -----p.join()方法
去取钱之后程序start开启子进程,子进程sleep等3s,3s之后子进程取钱,取完钱子进程就结束了
结束了之后p.join的阻塞解除,然后主进程执行join后面的代码
p.join阻塞主进程,直到p子进程结束之后就结束阻塞了,接着往下执行下面的代码
上面这样修改 主程序又阻塞了,
multiprocessing.Process创建进程对象 target传要执行的函数,args传递参数
进程对象.start()启动进程
主进程和子进程就是异步执行
如果主进程中的代码已经结束了,子进程还没有结束,主进程会等待子进程
p.join() 就是主进程阻塞在join的位置,等待p进程结束,结束之后主进程才接着往下走
windows操作系统中创建进程的语句一定要放在if __name__ == '__main__':条件语句下
windows上开发代码创建进程的代码需要放到:if __name__ == '__main__':里
因为windowos上创建子进程,子进程会把整个父进程文件里的内容所有代码再执行一次
p = Process(target=func, args=(100,))和p.start()不放在if __name__ == '__main__':里
创建一个子进程又执行一次p = Process(target=func, args=(100,))和p.start()又创建一次子进程
然后创建的子进程又执行一次p = Process(target=func, args=(10 ,))和p.start()又创建一次子进程
就会一直创建进程直到内存gg报错
放在if __name__ == '__main__':里,作为一个子进程再去执行if __name__ == '__main__':里的代码
子进程的name是自己模块的名字不等于__main__了,所以就不会再去执行反复创建进程的事情了
4:开启多个子进程 for循环创建
from multiprocessing import Process import os import time def func(i): time.sleep(1) print(f"子进程{i}干的事情:{os.getpid()},父进程:{os.getppid()}") if __name__ == '__main__': p_list = [] for i in range(10): p = Process(target=func, args=(i+1, )) p.start() p_list.append(p) for p in p_list: p.join() print("主进程") # 这样10个进程都还是异步的,所有的进程结束后才打印"主进程"
5:子进程之间的数据隔离问题 进程和进程之间的数据
from multiprocessing import Process def func(): global n n = n-1 print(n) if __name__ == '__main__': n = 100 p = Process(target=func) p.start() print(f"主进程的n:{n}") # NameError: name 'n' is not defined
if __name__ == '__main__':里定义的n在子进程里使用会报错,子进程里无法使用,没有获取到n
from multiprocessing import Process n = 100 def func(): global n n = n - 1 print(n) if __name__ == '__main__': p = Process(target=func) p.start() print(f"主进程的n:{n}") 打印: 主进程的n:100 99
n不放在if __name__ == '__main__':里就可以运行不会报错了,在里头就不行
windows操作系统中创建子进程,子进程会把父进程模块的所有代码再执行一遍,主进程里n=100
子进程里也执行一次n=100并执行func函数,子进程也申请空间开辟n存储100,然后子进程执行func函数n-1了
from multiprocessing import Process n = 100 def func(): global n n = n - 1 print(n) if __name__ == '__main__': for i in range(10): p = Process(target=func) p.start() print(f"主进程的n:{n}") 进程与进程之间是完全数据隔离的,没有办法对一个数据进程操作,
上面每个进程里都又开辟了一个空间把n=100存储到了自己的空间里,然后调用func函数来n-1,在自己进程空间里n减的数据和主进程和其他进程的数据完全没有关系
多进程的特点
6:开启进程的另外一种方式 继承
from multiprocessing import Process import os class MyProcess(Process): def __init__(self, arg1, arg2, arg3): Process.__init__(self) # Process.__init__(self, group=None, target=None, name=None, args=(), kwargs={}, daemon=None) # super().__init__() self.arg1 = arg1 self.arg2 = arg2 self.arg3 = arg3 def run(self): """run方法的内容就是想要放在子进程当中想要执行的内容""" print(f"子进程:{os.getpid()}", self.arg1, self.arg2, self.arg3) self.walk() # walk方法会在子进程中执行 def walk(self): """正常情况下这个waik不会默认执行""" print(f"子进程:{os.getpid()},walk") if __name__ == '__main__': p = MyProcess(1, 2, 3) p.start() # 会默认调用run方法,开启一个进程之后只有通过run方法才能实现子进程之内的代码 p.walk() # p.walk()这里调用其实是进行的一个同步调用,这个waik方法直接在主进程中调用并没有放在子进程当中执行 # 如果想walk在子进程当中执行可以在run方法里self.walk来调用walk方法 # run方法里的代码都是子进程中执行的,self.walk也会在子进程当中执行walk方法 print(f"主进程:{os.getpid()}") 类继承方式创建一个子进程: 1:必须创建一个类 必须继承Proces这个类 2:必须实现一个run方法 使用的时候: 以前实例化Process现在实例化MyProces这个类得到一个对象 使用对象调用start方法就成功开启了一个子进程 类继承的方式来实现开启子线程调用任务函数的时候如果需要传参使用__init__方法,实例化类的时候就传参 原来的Process也有init初始化方法,MyProcess重构了init方法初始化的时候只调用自己的init 不会调用Process的init,这里为了避免发生问题调用一下Process这个父类的init方法 因为p.start()调用父类的start()方法的时候需要用到父类init里面定义的各自属性比如self._closed = False 所有需要调用父类的构造方法才不会报错
7:多进程异步编程实现服务端和多个客户端聊天
# server服务端代码 import socket from multiprocessing import Process def talk(conn): while 1: msg = conn.recv(1024) print(msg) conn.send(b"fuwuduanmsg") conn.close() if __name__ == '__main__': addr = ("127.0.0.1", 9999) sk = socket.socket() sk.bind(addr) sk.listen() while 1: conn, addr = sk.accept() p = Process(target=talk, args=(conn,)) p.start() conn.close() sk.close() 主进程就循环一直accept拿到一个链接,之后起一个多进程把这个链接丢给子进程 让子进程拿着这个链接聊天,多个进程和多个进程直接并没有关系 上面这就是异步编程:主进程一直干活接收别人的链接,子进程去做聊天的事情 多进程异步编程实现服务端和多个客户端聊天
# client客户端代码 import socket addr = ("127.0.0.1", 9999) sk = socket.socket() sk.connect(addr) while 1: sk.send(input(">>>>>>").encode("utf8")) msg = sk.recv(1024).decode("utf8") print(msg) sk.close()
8:multiprocessing多进程模块里常用的一些方法
p.start():开启一个进程,启动进程,并调用该子进程中的p.run()
p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
p.terminate():强制终止(结束)进程p,不会进行任何清理操作,没有执行完就想让进程结束使用
如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
调用这个方法就是异步的过程
p.is_alive():如果p仍然运行,返回True,否则返回False
p.join([timeout]):让主线程等待子进程p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。
timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程
p.pid():查看这个进程的进程id
p.name():查看这个进程的名字
9:守护进程 p.daemon = True
守护进程:python中开启一个进程可以设置成守护进程
1:守护进程会在主进程代码执行结束后就终止(随着主进程的结束而结束)
2:守护进程需要在进程start开启之前设置
3:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
正常情况下子进程里还能开启子进程,一般很少再在进程里再开启进程
正常情况下的子进程不会随着主进程的代码执行结束而结束的,正常的子进程没有执行完的时候主进程要一直等着子进程结束
from multiprocessing import Process import time def cal_time(): """报时器函数""" while 1: time.sleep(1) print("过去了1s") if __name__ == '__main__': p = Process(target=cal_time) p.daemon = True # 设置守护进程:一定在start开启进程之前设置 p.start() for i in range(100): time.sleep(0.1) print('*' * (i + 1))
# 子进程里还可以再开启子进程,前提是子进程里不设置守护进程,设置守护进程的子进程内部还开启子进程就会报错
一般很少这样做 from multiprocessing import Process import time import os def func(): print(os.getppid(), "-------------------------") def cal_time(): """报时器函数""" p1 = Process(target=func) p1.start() while 1: time.sleep(1) print(os.getpid(), "过去了1s") if __name__ == '__main__': p = Process(target=cal_time) p.start() for i in range(100): time.sleep(0.1) print('*' * (i + 1))
守护进程: 1:会随着主进程的代码执行结束而结束,不会等待其他子进程的 2:守护进程需要再start之前设置 3:守护进程中不能再开启子进程 from multiprocessing import Process import time import os def func(): print(os.getppid(), "-------------------------") time.sleep(15) print(os.getppid(), "-------------------------") def cal_time(): """报时器函数""" while 1: time.sleep(1) print(os.getpid(), "过去了1s") if __name__ == '__main__': p1 = Process(target=cal_time) p1.daemon = True p1.start() p2 = Process(target=func) p2.start() for i in range(100): time.sleep(0.1) print('*' * (i + 1)) # P1设置守护进程,p2是一个正常的进程,主进程需要执行10s,子进程p2需要执行15s # 主进程会等待p2进程执行完 # 守护进程p1在主进程的代码执行结束后就会结束了,不会等到子进程p2执行完了主进程结束之后再执行 # 守护进程会随着主进程的代码执行结束而结束,而不是随着主进程的结束而结束
守护进程等着其他子进程的代码执行结束后才结束执行: 主进程内使用join就可以了 from multiprocessing import Process import time import os def func(): print(os.getppid(), "-------------------------") time.sleep(15) print(os.getppid(), "-------------------------") def cal_time(): """报时器函数""" while 1: time.sleep(1) print(os.getpid(), "过去了1s") if __name__ == '__main__': p1 = Process(target=cal_time) p1.daemon = True p1.start() p2 = Process(target=func) p2.start() for i in range(100): time.sleep(0.1) print('*' * (i + 1)) p2.join() # p2.join()代码不执行完主进程的代码就没有结束,p2子进程执行完了才会执行p2.join() # 执行完了p2.join()那么主进程就执行完了,主进程完了守护线程p1才会结束执行 # 所有间接让p1子进程等待其他子进程p2的代码执行结束后才结束执行 # p2进程不执行完主进程的p2.join()就会一直阻塞,主进程的代码会等着p2进程执行完后才结束主进程的代码 # 主进程代码没有运行结束那么p1这个守护线程的计时器就会一直运行直到主进程结束
# p.terminate():结束一个进程,但是这个进程不会立刻被杀死 from multiprocessing import Process import time import os def func(): print("func is alive", os.getpid()) time.sleep(5) print("func is over", os.getpid()) if __name__ == '__main__': p = Process(target=func) p.start() print(p.is_alive()) # True time.sleep(0.1) p.terminate() # 关闭进程,这是异步操作,下达杀死进程命令之后不会等进程杀死之后有操作结果才接着往下执行下面的代码 # 这里主进程通知操作系统把p进程杀死然后自己就去干其他事情了 ------异步操作 print(p.is_alive()) # True time.sleep(1) print(p.is_alive()) # False p.terminate()想要关闭进程p,python程序只是使用调用操作系统提供的接口来让进程关闭,程序只是向操作系统下达指令 操作系统什么时候关闭不一定,所有需要等待一点点时间p进程才会被操作系统关闭, 调用p.terminate()后p进程肯定会被杀死,只是杀死的过程有一点点延时,不会被立即杀死
# 查看进程的pid和name,修改进程的name名字 from multiprocessing import Process import time import os def func(): print("func is alive", os.getpid()) time.sleep(5) print("func is over", os.getpid()) if __name__ == '__main__': p = Process(target=func) p.start() print(p.name, p.pid) # Process-1 8968 p.name = "李二狗" print(p.name, p.pid) # 李二狗 5404 # 子进程内想查看进程name和pid from multiprocessing import Process import time import os class MyProcess(Process): def run(self): print(self.name, self.pid) # MyProcess-1 16176 print("func is alive", os.getpid()) time.sleep(5) print("func is over", os.getpid()) if __name__ == '__main__': p = MyProcess() p.start() print(p.pid, p.name) # 16176 MyProcess-1 # p就是一个对象,就是MyProcess类的一个实例化对象self,这里的p.pid和p.name都能在MyProcess使用self对象调用到
pid和name,daemon等属性都在Process类的init构造方法里
10:进程锁的使用 Lock()
假设一个文件: 子进程访问文件,文件内有一个计数假设10,进程1读10操作数据比如10-1=9,然后把9写进文件 多进程一起操作文件,多个进程都同时去读文件有可能都拿到10这个计数,进程1进行10-1=9 进程2进行10-1=9,然后多个进程都把数据写入文件,多个进程就往这个文件里写了多次都写入9 抢票: 放出10张票100个人来抢,100个人都读这个文件里的10,有可能很短时间内全部用户都打开这个文件读取这个10 然后用户买票,买到票后票的数量需要-1,-1后要把剩余的票的数目写回去,100个进程同时拿到余票10这个数据都能买票 所以用户付钱买票之后-1往文件写余票数,有可能显示100个用户才买了一张票余票文件显示9,但是同时卖给了100个人100张票 -----数据不安全,不安全的并发 因为进程之间数据互相隔离,数据文件又谁都能读写操作,多个进程可能同时拿走数据进行操作然后同时写到文件,文件的内容以最后一个进程写入文件的数据为准 表现为数据10被n个进程都拿走都减了n次1之后文件存储的还是9------这时候可以使用进程锁 类似文件加锁,先读取文件的进程1操作文件的时候文件就上锁,进程1拿到数据之后操作数据然后把数据写入文件,写入文件之后才解锁, 其他进程都在门口等着,竞争关系,随机一个进程再读取文件数据再上锁,解锁 文件加锁之后同一时间只能一个进程操作文件,直到解锁为止,不解锁其他进程只能死等 以前多进程读取文件是异步的,都随意读取操作文件,现在加锁之后程序变成同步的了,读取文件操作文件动作变成同步的了,同一时间只能一个进程做这个事情 意味着程序执行效率下降了,但是保证了数据的安全,效率的下降也要保证数据的安全,锁的产生就是为了保证数据的安全,安全的并发
lock = Lock():Lock锁是一个类,lock = Lock()实例化的时候就类似真的买一把锁,拿到一把锁
lock.acquire():上锁,需要锁,拿钥匙(对于同一把锁lock,accquire上锁之后不能再次accquire上锁,需要等到release解锁之后才能再次上锁)
lock.release():解锁,释放锁,换钥匙
锁:就是在并发编程中保证数据安全
from multiprocessing import Lock from multiprocessing import Process def func(num): print(num) if __name__ == '__main__': lock = Lock() for i in range(20): p = Process(target=func, args=(i,)) p.start() p.join() 让这20个进程执行按照顺序执行,默认20个进程之间是没有规律和顺序的 1:使用join 2:上锁:上锁实现不了,只能使用join
抢票软件 不安全的并发 票的数量存储在一个ticket文件里:{"count": 4} 还有四张票 from multiprocessing import Lock from multiprocessing import Process import json import time import random def search(i): with open("ticket") as f: print(f'{i}查询,还剩下{json.load(f)["count"]}张票') def get(i): with open("ticket") as f: ticket_num = json.load(f)["count"] time.sleep(random.random()) if ticket_num > 0: with open("ticket", "w") as f: json.dump({"count": ticket_num - 1}, f) print(f"{i}买到票了") else: print(f"{i}没有买到票") def task(i): search(i) get(i) if __name__ == '__main__': lock = Lock() for i in range(20): p = Process(target=task, args=(i,)) p.start() 上面的代码没有加锁,不安全的并发 运行起来就4张票被20个人都抢到了,而且抢完了之后ticket文件里的票还有3张:{"count": 3} 所以程序有问题 加锁以后让并发的共享的数据使用起来更加安全 from multiprocessing import Lock from multiprocessing import Process import json import time import random def search(i): with open("ticket") as f: print(f'{i}查询,还剩下{json.load(f)["count"]}张票') def get(i): with open("ticket") as f: ticket_num = json.load(f)["count"] time.sleep(random.random()) # 模拟网速延迟 if ticket_num > 0: with open("ticket", "w") as f: json.dump({"count": ticket_num - 1}, f) print(f"{i}买到票了") else: print(f"{i}没有买到票") def task(i, lock): search(i) # 查看票 lock.acquire() get(i) # 买票 lock.release() # 查票的时候都可以查,查票过程可以不加锁, # 买票加锁:只能有一个进程调用get买票函数, # 如果其他进程想同时调用get买票函数那么就会其他进程就会阻塞等待,等到上一个买票的进程函数调用release()释放 if __name__ == '__main__': lock = Lock() for i in range(20): p = Process(target=task, args=(i, lock)) p.start() 抢票:为了保证数据的安全必须要加锁,有几张票就被几个人抢到
加索:同一时间只能有一个进程在执行加锁中间的代码,降低效率
11:进程中信号量的使用 Semaphore
信号量:本质也是一把锁(加了计数器的锁),但是这把锁有好几把钥匙,有几把钥匙就能让同时最多几个人进房间
from multiprocessing import Semaphore sem = Semaphore(4) # 实例化一个对象得到一把锁配备4把钥匙,默认value=1是1把钥匙 sem.acquire() # 拿走1把钥匙,还剩下3把钥匙可以进行3次acquire print(0) sem.acquire() # 拿走1把钥匙 print(1) sem.acquire() # 拿走1把钥匙 print(2) sem.acquire() # 拿走1把钥匙 print(3) # 锁sem拿走4把钥匙都没用问题,因为这个锁就是有4把 sem.acquire() # 拿了4把钥匙再拿的时候就会阻塞在这里了.说明没有钥匙了(除非前面4个人release还一把钥匙才可与继续acquire拿钥匙) print(4)
信号量Semaphore就是限制代码里某段代码的执行最多允许几个并发,避免并发数过多
# 迷你唱吧 20个人 同一时间只能4个人进去唱歌 from multiprocessing import Semaphore from multiprocessing import Process import time import random def sing(i, sem): sem.acquire() # 进去ktv之前需要拿钥匙才能进,没有拿到钥匙只能在这里等着 print(f"{i}:enter the ktv") time.sleep(random.randint(1, 10)) # 唱歌1-10s print(f"{i}:leave the ktv") sem.release() # 唱完之后还钥匙 # 现在只能同一时间最多进去4个,出来一个马上又进去一个----同一时间只能4个在里面 if __name__ == '__main__': sem = Semaphore(4) # 信号量设置最多4个人也就是4个子进程来一起运行sing函数 for i in range(20): p = Process(target=sing, args=(i, sem)).start()
12:事件和队列 Event
现在学的所有的阻塞都是同步阻塞:
recv,accept,input,sleep这些阻塞只能阻塞一个进程,写在那个进程里就阻塞哪个进程,不能同时阻塞多个进程,这些阻塞都是同步的
同时阻塞多个进程:
lock锁阻塞多个进程,1个lock10个进程都来抢lock,至少能同时阻塞9个进程,锁就能实现异步阻塞(锁信号量都是实现异步阻塞的手段)
事件:也可以实现异步阻塞
事件 标志,通知 同时 使所有的进程都陷入阻塞(异步阻塞),同时让所有的进程都恢复不阻塞了
from multiprocessing import Event # Event 事件 e = Event() # 实例化一个事件,事件里有一个标志(交通信号灯,实例化了一个事件对象相当于在路口立了一个红绿灯) # 所有的进程就相当于过往的一辆辆的车,信号灯红灯所有车都得停止,绿灯所有车都得走 e.set() # 将标志变成非阻塞(交通灯变绿色),e实例化后默认是阻塞(红灯),e.set() 变成绿灯 e.wait() # 刚实例化出来得一个事件对象,默认的信号是阻塞信号,wait动作红灯就等待阻塞在这里,绿灯不阻塞 # 执行到wait要先看灯,绿灯行红灯停,如果在停的过程中灯绿了就变成非阻塞了 e.clear() # 将标志又变成阻塞(交通灯变红) e.is_set() # 看事件是否阻塞,True就是绿灯 False就是红灯
from multiprocessing import Event # Event 事件 from multiprocessing import Process import time import random def traffic_light(e): while 1: if e.is_set(): # 如果是绿灯 time.sleep(3) print("红灯亮") e.clear() # 绿变红 else: # 如果是红灯 time.sleep(3) print("绿灯亮") e.set() # 红变绿 def car(i, e): e.wait() # 假设这时候是绿灯不影响直接往下走,如果是红灯就阻塞在这里等着变成绿灯才解除阻塞 print(f'{i}车通过') """ for i in range(100): # i=0 余0 sleep # i=1 余1 不sleep # i=2 余2 不sleep # i=3 余3 不sleep # .... # i=6 余0 sleep # i=0时候sleep,i=1-5没有sleep一次性来了5辆车,i=6的时候又sleep # 假设每一次数字打印出来等于一辆车,第1次等一下来一辆车,马上走5辆车,又等一下来1辆车...., # 为了模拟好几辆车同时来等着这个灯,当灯变绿了之后好几辆车同时走, # 模拟红绿灯变化变红变绿,然后车来一波走一波, # i=6,这里每一次同时来6辆车,然后等一会,这6辆车同来同走 if i % 6 == 0: time.sleep(random.randint(1, 3)) print(i) """ if __name__ == '__main__': e = Event() # 立一个信号灯,现在是红灯 tra = Process(target=traffic_light, args=(e,)) # 红绿灯的进程 tra.start() # 起一个进程来控制红绿灯 for i in range(100): if i % 6 == 0: time.sleep(random.randint(1, 3)) car_pro = Process(target=car, args=(i, e)) car_pro.start() # 起100个进程,100个进程每次让快速通过6个,每6个sleep睡一会, # 每通过一辆车起一个进程控制它,过路口看一眼红灯还是绿灯,红灯停着,绿灯就过了 # car_pro和上面的car函数不能名字相同,不能car = Process(target=car, args=(i, e)) # 这样起多进程就不会报错:EOFError: Ran out of input # 否则car_pro.start()这里会EOFError: Ran out of input报错 现在整个程序: 绿灯亮就有车通过,红灯亮不会有车通过. 事件:事件对象同时控制好多个进程的阻塞与继续
锁和信号量:锁和信号量本质都是锁,只是有几把钥匙不同而已
事件:以一个事件的标志去控制所有的进程等待或者是通行
13:队列 作用于进程之间的通信 queue和JoinableQueue
进程之间数据通信和数据共享:
lock锁对象在进程1里acquire了,在进程2里lock对象再acquire就阻塞住了。这两个进程之间的lock是共享的(进程之间的数据共享)
使用队列来实现进程之间通信:
进程共享的队列和普通的queue和双端队列deque都不一样,进程共享的队列还是multiprocessing里面的队列,
除了导入的地方不一样,其他的和普通的queue队列使用方法都一样
队列:先进先出,get,put方法,往里放东西然后往里拿东西,先进先出
1:进程之间通信可以使用multiprocessing模块的 Queue队列模块,别的队列模块不顶用 2:队列有两种创建方式 1:不传参数:q = Queue() 这个队列就没有长度限制,put存进去多少个数据都不会阻塞 2:传递参数:q = Queue(3) 创建一个有最大长度限制的队列 3:队列提供两个重要的方法 put:对于不定长的队列来说往队列put数据永远不会阻塞,但是有长度限制的队列队列存满了再往里put就阻塞了 get:只要队列里有数据就能拿到,但是队列里没有数据再get那么就阻塞了 4:q.qsize() 1:有的操作系统上调用这个函数会报错 2:查看队列的数据大小,拿到的大小不一定是准确的,进程获取队列大小的同时,另外一个进程同时放一个数据到队列里,就可能会有问题
from multiprocessing import Queue q = Queue(3) # 没有指定队列长度这就是一个不定长的队列,可以put和get数据 # Queue队列实例化的时候给了value值那么就是一个有长度的队列了,队列可以最多存储3个数, # 再put第4个数据的时候代码就会阻塞,等着这个队列里面的3个数有人拿走了这里才能put往队列里放数据 q.put(1) q.put(2) q.put(3) print(q.get()) # 1 print(q.get()) # 2 print(q.get()) # 3 # print(q.get()) # 代码阻塞在这里,如果队列里没有值了就会阻塞等待有一个值 print(q.qsize()) # 0 # 查看队列的数据大小,不一定是准确的,进程获取队列大小的同时,另外一个进程同时放一个数据到队列里,就可能会有问题
可以通过队列实现 主进程和子进程的通信 子进程和子进程之间通信 from multiprocessing import Process from multiprocessing import Queue import os def q_put(q): while 1: q.put("hello") def q_get(q): print(q.get(), os.getpid()) # hello 10164 if __name__ == '__main__': q = Queue() p = Process(target=q_put, args=(q,)) p.daemon = True p.start() p1 = Process(target=q_get, args=(q,)) p1.start() p1.join() print(q.get(), os.getpid()) # hello 2668
14:生产者消费者模型
我要生产一个数据 然后传给一个函数 让这个函数依赖这个数据进行运算 拿到结果 -----这是一个同步的过程(函数需要永远等着数据ok才运行,一条线运算)
生产者消费者:
假设生产数据比较快,搞一个生产数据的进程
假设操作消费数据比较慢,搞一堆消耗数据的的进程,从生产方拿数据
异步操作,异步操作比较快
生产者和消费者需要通信,生产者把生产的数据放在一个队列里,消费者门从队列里一个个拿数据然后处理就行了
from multiprocessing import Queue from multiprocessing import Process import time # 做包子和吃包子 def producer(q): # 生产者,快速生产100个包子 for i in range(100): q.put(f"包子{i}") def consumer(q): # 消费者,吃包子很慢,1s才吃一个 for i in range(100): time.sleep(1) print(q.get()) if __name__ == '__main__': q = Queue(10) # 包笼,生产者做包自往里丢,消费者买了在这里拿 p = Process(target=producer, args=(q,)) p.start() c = Process(target=consumer, args=(q,)) c.start() c1 = Process(target=consumer, args=(q,)) c1.start() 生产包子很快,生产100个包子仅需要0.000001s,吃包子很慢1s才吃一个 那么生产线程一次性就把100个包子丢队列了,吃的又很慢,这样数据就会占用内存----所以限制包笼最多放10个包子 有人买多生产,没人买少生产,包笼最多放10个包子吃一个再补一个,吃一个再补一个 上面的代码一个生产者进程和一个消费者进程,跑的慢,后面一个包子生产一个包子消费,相当于同步编程了 可以创建多个消费者来吃包子 这样限制队列大小: 首先对于内存来说,每次只有很少的数据再内存里 对于生产与消费之间的不平衡来说增加消费者或者增加生产者来调节效率 (现在生产包子不到1s生产100个,而一个进程吃1个需要1s,可以起很多个进程来处理消费数据----) (如果生产数据实在太快了,而消费数据特别慢,只能控制队列的大小来控制整体内存当中的数据不要太大)
同步程序:
生产数据 使用数据 使用数据阻塞或者生产数据阻塞会阻塞整个生产消费的流水线,同步程序效率不行
异步程序:
主进程生产数据,子进程使用数据----假设500个子进程处理数据,1个子进程生产数据,生产太慢,会造成部分子进程闲置
子进程生产数据,子进程处理数据----
n个子进程生产数据
m个子进程消耗数据
如果生产的快,消耗慢,那么数据就会溢出,造成队列里面数据过多占用内存
如果生产的慢,消费快,资源不足,不够消费者使用
如果增加生产者--让生产更快
如果消费慢,减少生产者,如果生产者消费者任然存在不平衡,找一个容器放数据,约束容器的容量,让生产者和消费者之间工作平衡起来了
from multiprocessing import Queue from multiprocessing import Process import time import random def producer(q, food): for i in range(5): q.put(f"{food}{i}") print(f"生产了一个{food}") # time.sleep(random.randint(1, 3)) time.sleep(random.random()) q.put(None) def consumer(q, name): # 消费者,吃包子很慢,1s才吃一个 while 1: food = q.get() if food is None: break print(f"{name}吃了{food}") if __name__ == '__main__': q = Queue(10) # 包笼,生产者做包自往里丢,消费者买了在这里拿 p = Process(target=producer, args=(q, "包子")) p.start() p2 = Process(target=producer, args=(q, "骨头")) p2.start() c = Process(target=consumer, args=(q, "李二狗")) c.start() c = Process(target=consumer, args=(q, "李三狗")) c.start() c = Process(target=consumer, args=(q, "李四狗")) c.start() 队列: 多进程之间使用队列事件是安全的,队列是进程使用数据安全的,不需要加锁也不会出现重复的现象 队列queue里内置了一把锁,进程1取数据的同时进程2不会在队列里同时去取数据进行操作 多进程之间抢占资源对于队列来讲永远不会发生数据不安全的情况 队列不会不安全 通过queue 生产者消费者模型 1:消费者要处理多少数据是不确定的 2:所以消费者只能使用while循环来处理数据,但是while循环无法结束 3:结束消费者的while循环需要生产者发生结束信号 4:有多少个消费者就需要发生多少个信号 现在有2个生产者,3个消费者只接收3个信号,需要让收发结束信号平衡,且计算结束信号够不够,少一个就结束不了 5:发生的信号数量需要根据生产者和消费者的数量进行计算,所以非常不方便 所以就有了另外一种队列:JoinableQueue
JoinableQueue:队列可以感知到数据的消耗和处理 put put完所有数据之后,最后执行一下q.join() get 处理数据 task_done(告诉生产者这一个数据消费完了)
join() 阻塞住代码,直到所有的数据消耗完了每次消耗完发送一个task_done,
类似JoinableQueue内部一个计数器,每生产一个数据计数+1,每次消耗一个数据后调用task_done计数-1
等待计数器归0的是join这里的阻塞就解除了
task_done JoinableQueue队列根据task_done来计数的,每get拿走一个数据,处理数据,
然后调用task_done返回给这个队列,这个队列就知道有个数据被消耗了
一堆数,每个task_done就计一个,当每个数都被task_done了队列就能感知到了,join方法能感知到
join会阻塞程序等待所有的任务都task_done才解除阻塞
# 使用JoinableQueue实现生产者消费者模型:解决生产者消费者结束问题 from multiprocessing import JoinableQueue from multiprocessing import Process import time import random def producer(q, food): for i in range(5): q.put(f"{food}{i}") print(f"生产了一个{food}") time.sleep(random.random()) q.join() # 阻塞等待在这里,等待消费者把所有的数据都处理完,等待一个信号结束 def consumer(q, name): # 消费者 while 1: food = q.get() print(f"{name}吃了{food}") q.task_done() # 告诉数据处理完了(拿一个数据就通知一下,直到队列所有数据都消费完了那么生产者q.join那里能感知到,q.join就不阻塞了,生产者就结束了) if __name__ == '__main__': q = JoinableQueue() p1 = Process(target=producer, args=(q, "包子")) p1.start() p2 = Process(target=producer, args=(q, "骨头")) p2.start() c1 = Process(target=consumer, args=(q, "李二狗")) c1.daemon = True c1.start() c2 = Process(target=consumer, args=(q, "李三狗")) c2.daemon = True c2.start() c3 = Process(target=consumer, args=(q, "李四狗")) c3.daemon = True c3.start() p1.join() # 等待p1代码执行完毕 p2.join() # 等待p2代码执行完毕 # p1和p2结束了,那么consumer就跟着结束了,因为consumer是守护进程,跟着主进程结束而结束 # p1和p2在生产完了之后q.join()这里阻塞等着,等待消费者把所有数据都处理完p1和p2的q.join()这里就解除阻塞,p1和p2就结束了 # 处理进程里每处理完一个数据就给一个task_done(),q.join()的阻塞就是根据task_done()信号来确定是不是已经把所有的数据都处理完了 # 所有的数据处理完了p1和p2就解除q.join()的阻塞了,那么p1和p2的生产者函数执行完毕意味着生产者数据都生产完了, # 且消费者把所有的数据都消耗完了
代码解析:
生产者生产的数据全部被消费——>生产者的进程先结束——>主进程代码执行结束——>消费者守护进程结束
这样再也不也计算发送多少个结束信号了,
q.join()所有的producer生产者进程都在这等着,等着消费者把所有数据消耗完,q.join就解除阻塞,生产者进程也就结束
所有的进程都在这个地方都结束了
实现的思想分析:
生产者:put数据....put数据,join(这里等着生产者放进队列q里的所有数据被消费者取出都执行了taskdone为止才停住,
假设生产者放进去队列q3个数据那么消费者需要taskdone3次,生产者的q.join才能解除阻塞才能结束)
生产者最后一句代码是q.join(),执行完了q.join代表生产者结束了,生产者结束了我希望消费者跟着生产者结束一起结束
在主进程执行producer.join()让主进程等待生产者结束,主进程等待生产者结束说明运行到这里生产者代码就已经结束了
生产者代码结束说明q.join运行完结束了,q.join结束了说明消费者已经把生产的所有数据都处理完了,如果设置消费者为守护进程
那么主进程这个地方等待生产者结束,生产者结束说明消费者也要结束了,如果主进程里没有其他代码了,设置消费者为守护进程
消费者进程就会随着主进程结束而自动结束,而主进程随着生产者进程结束而结束,所有消费者进程会和生产者进程一起结束,
而生产者进程结束需要依赖消费者进程消费完所有生产者生产的数据才结束-----这就是整个过程
需要使用q.join才能直到消费者什么时候把全部数据消费完了
消费者:get数据,处理数据,taskdone(告诉q已经一个任务执行完了,已经处理完一个数据了)......
15:管道PiPE
ipc机制:队列
ipc机制:pipe(管道)
from multiprocessing import Pipe p1, p2 = Pipe() # 拿到的是一对,管道的两端,端1和端2 p1.send("hello") print(p2.recv()) # hello p2.send('hi') p2.close() # 管道一端已经没有了 print(p1.recv()) # hi # print(p1.recv()) # EOFError # close消耗管道的一端,报错:EOFError 如果p2没有关闭:p1.recv时候管道里有数据就拿没有数据就阻塞 如果p2没有close关闭,会阻塞在p1.recv()这里而不是报错,因为没有关闭p2总想着p2可能会某时发送一条消息过来 如果p2close关闭:p1.recv时候管道里有数据就拿没有数据就报错 如果p2被close关闭,那么没有p2发送消息了,管道里没有消息了再recv就会引发EOFError报错 pipe 的recv接收端需要根据EOFError报错来结束程序,try一下来结束程序, 管道:(双端通信的东西,并发的时候数据不去安全)管道用来通信的,支持双向通信 通信的是一条管道, 左边管道进去,右边管道出来 右边管道进去.左边管道出来
管道实例一: from multiprocessing import Pipe from multiprocessing import Process def func(p): foo, son = p # foo.close() print(son.recv()) print(son.recv()) if __name__ == '__main__': foo, son = Pipe() p = Process(target=func, args=((foo, son),)) p.start() foo.send("hello") foo.close() # 发送消息后关闭管道的一端 这样主进程关闭管道的一端后子进程两个recv不报错了,会阻塞 因为子进程里还有foo在使用,所以其实没有关闭,相当于父进程的对象foo给他用,然后又开了子进程又想用foo,
copy了一个foo放到了子进程里给子进程里用,关闭的只是父进程的foo,并没有影响子进程里面的 所以在子进程里还是需要foo.close()关闭管道的一端,运行起来子进程里才会报错 所以父进程里有foo和son,子进程里也有一个foo和son,两个进程里都需要关闭foo和son才是真正关闭管道的一端:如下 from multiprocessing import Pipe from multiprocessing import Process def func(p): foo, son = p foo.close() while 1: try: print(son.recv()) except EOFError as f: print(f"发生错误了:{f}") break son.close() if __name__ == '__main__': foo, son = Pipe() p = Process(target=func, args=((foo, son),)) p.start() son.close() foo.send("hello") foo.send("hello") foo.send("hello") foo.send("hello") foo.send("hello") foo.close() # 发送消息后关闭管道的一端 管道: foo, son = Pipe() 多个进程里使用管道,如上,主进程里有foo和son,子进程里也有foo和son备份 如果主进程消息发送结束了,想要结束这个程序才对 现在并没有结束,因为子进程while循环里还在一直等待son.recv()接收数据呢 必须发送数据的一端关闭了才能触发 EOFError,才能认为你不会给我发数据了 子进程里foo.close()关闭了管道的一段,父进程也需要调用foo.close()关闭管道的另外一段才会报错 如果父进程没有调用foo.close()关闭管道的另外一端子进程的recv会一直阻塞住的 所以只有所有使用foo和son的进程全部都调用foo.close()才会真正关闭foo,否则不会真的关闭 关闭了pipe管道的一端foo后子进程第二次recv就会触发错误,使用try except来捕捉停止错误后break停止while循环接收数据 这样就能正常结束 这样不管主进程使用foo发送多少消息,只要发完后记得真正关闭了foo,子进程能收到且接受到所有数据后正常退出
管道pipe的特点: 应该特别注意管道端点的正确管理问题。如果是生产者或消费者中都没有使用管道的某个端点,就应将它关闭。
这也说明了 为何在生产者中关闭了管道的输出端,在消费者中关闭管道的输入端。
如果忘记执行这些步骤,程序可能在消费者中的recv( )操作上挂起(阻塞)。
管道是由操作系统进行引计数的,必须在所有进程中关闭管道后才能生成EOFError异常。
因此,在生产者中关闭管道不会有任何效果(主进程关闭son不会影响在子进程中使用son这个管道一端的),除非消费者也关闭了相同的管道端点。
所以开启进程的时候不用的pipe的端点应该即时关闭,只有及时关闭才能在recv这里成功的触发EOF错误,
触发错误接收的过程当中如果发送端已经不发送了那么接收端关了就行了
如上的例子:
子进程一直使用son没有使用foo,上来就把foo关了
主进程一直使用foo,没有使用son,上来就把son关了
pipe管道也可以实现生产者消费者模型: 生产者进程拿pipe的一端pip1,管道的一端往管道里放数据 消费者拿着pipe的另外一端pip2,管道的另外一端从管道里取数据 管道和队列: 一个数据两个进程同时取抢占它,pipe可能同时被两个进程拿走,pipe默认并发数据是不安全的, pipe实现的时候只发送数据,几个进程资源竞争情况可能两个进程把同一个值拿走, pipe内部是基于socket做的,socket接收数据时候情况如下,数据已经发送到操作系统了 两个进程同时到操作系统上去取数据,有可能两个进程都copy了一份数据到各自的进程中了 同一个进程竞争两份资源是有不安全的情况的 就是因为不安全所以一般情况下不使用管道实现多并发 socket接收数据的时候: 1:等待接收 2:数据发送到操作系统 3:从操作系统把数据copy到进程中 队列 = 管道 + 锁(同一时间只能有一个进程访问管道拿数据,加锁)
使用pipe管道实现生产者消费者模型: from multiprocessing import Pipe from multiprocessing import Process from multiprocessing import Lock def func(p, l): foo, son = p foo.close() while 1: try: l.acquire() print(son.recv()) l.release() except EOFError as f: l.release() # try里面如果执行了son.recv报错了,l.release()没有执行那么就没有释放锁,所以就算报错也需要释放锁才没问题 # 比如消费者进程1 recv报错了,l还是acquire状态,消费者进程2又进来执行l.acquire()那么就一直阻塞造成代码程序阻塞 # 所以就算程序取数据取不出来报错了,l的锁还是要release借4解开 son.close() break def func2(p): foo, son = p son.close() for i in range(10): foo.send(i) foo.close() if __name__ == '__main__': foo, son = Pipe() l = Lock() p1 = Process(target=func, args=((foo, son), l)).start() p2 = Process(target=func, args=((foo, son), l)).start() p3 = Process(target=func, args=((foo, son), l)).start() p4 = Process(target=func2, args=((foo, son), )).start() p5 = Process(target=func2, args=((foo, son), )).start() p6 = Process(target=func2, args=((foo, son), )).start() p7 = Process(target=func2, args=((foo, son), )).start() son.close() foo.close() func函数一直往管道里recv func1函数一直往管道里send 起了3个进程执行func接收数据充当消费者 起了4个进程执行func1发送数据充当生产者 有些电脑不加锁可能报错,多个进程管道取数据抢占资源,加了锁就没问题了 消费者进程recv的时候加锁,其他进程执行到recv需要等着正在管道里取数据的进程释放锁 保证同一时间只有一个进程往管道里取东西,避免不安全的并发, 上面这样就使用管道就实现了一个和队列一样类似的效果
队列 = 管道 + 锁 管道: 1:双向通信 2:进程之间数据不安全 3:没有锁的机制 基于管道和锁 实现队列 进程之间通信一般使用队列 更高级方法 几个进程生产数据放在数据库或者文件里,这个文件是一个所有机器都共享的文件(现在使用队列或者文件都一般是一台机器上所有进程共享),
但是可能别的机器上跑者消费者consumer进程,分治思想,别的机器上跑处理任务占用别的机器的cpu
一起并肩处理任务,相当于多台服务器处理任务。但是数据原本存在自己文件里,
但是现在把数据存储在别的电脑上,我两需要数据的时候找别的电脑拿,这样安全快捷,存储数据也不占用我电脑的硬盘
不同电脑直接是基于网络来通信的,但是这里的队列,管道不支持基于网络通信,现在的队列管道都是只能在自己一台机器上一个程序上跑
未来可能producer生产者在10台机器上运行,需要一个传递消息的(类似队列,管道)消息中间件,不同机器上通过消息中间件放消息取消息
中间传递消息的通信都是基于网络的,有网才能通信,
现在比较流行的消息中间件:
1:rabitmq
2:kafka(高并发,存储多少数据都没问题,异常断开了数据也不丢失,数据安全,先到先服务)
其实用起来和队列管道差不多的,只不过从一台机器的几个进程通信变成了多台机器的多个进程之间通信
管道:做通信用的,没有加锁,多并发数据不安全
没有加锁的一个消息中转站,只能用于同一台电脑上传递一些数据
队列:
1:用在同一台机器的多个进程之间通信----同一台机器上不同进程通信的最好的方式
2:队列是基于管道实现的
16:进程通信,IPC通信的 IPC-Manager
4种进程间的通信:IPC
pipe:管道,双向通信,数据不安全 Queue:队列,管道+锁,数据安全,
双向通信(生产者也可以q.get拿数据也可以q.put生产数据,消费者也可以q.put存数据也可以q.get取数据,生产者put的数据可能消费者拿到,消费者put的东西可能生产者拿到----双向通信) JoinableQueue:队列queue基础上再实现put和get的一个计数机制,如果每次get数据之后发送task_done,put端接收到计数-1,直到计数为0能感知到
Manager:Manager是一个类,模块里提供了可以进行IPC通信(数据共享)的一个机制,提供了很多数据类型dict,list等
Manager模块里提供了很多数据类型来进行进程之间的数据共享,但是数据不安全,需要自己加锁,Manager包含了python当作所有的数据类型
Manager模块内部也实现了一个queue队列数据类型,这个Manager模块的queue队列数据类型在进程间共享数据就是安全的,其他数据类型都不安全,
根据数据类型会有变化的,manager模块有pipe,list,dict等各种数据类型,最终安全的只有queue队列,其他的数据都不安全
Queue基于pipe管道实现的,JoinableQueue是基于queue队列实现的
4中进程通信数据机制能实现进程间数据安全通信的只有queue队列,队列在原来的通信机制的基础上加了lock锁机制
from multiprocessing import Manager if __name__ == '__main__': m = Manager() d = m.dict() print(d) # {} # 这个字典d就是一个进程中间能够共享数据的字典 d["count"] = 0 print(d) # {'count': 0}
from multiprocessing import Process import time def func(dic): while 1: print(dic) time.sleep(3) if __name__ == '__main__': d = {} p = Process(target=func, args=(d, )) p.start() d["count"] = 0 这里使用普通字典,子进程一直打印空字典{},
主进程修改了d这个字典子进程感知不到一直是{}
因为主进程和子进程之间d这个字典不是共享的,主进程的d和子进程的d不同
from multiprocessing import Manager from multiprocessing import Process def func(dic): print(dic) if __name__ == '__main__': m = Manager() dic = m.dict({"count": 100}) l = [] for i in range(100): p = Process(target=func, args=(dic,)) p.start() l.append(p) [p.join() for p in l] print(dic) 子进程循环打印:{'count': 100} 如果想子进程使用dic,正常打印出来有效果的,主进程start进程之后必须出现p.join才能让子进程的代码正常打印dic
start之后必须进行一次join才行,暂时不明白详情 from multiprocessing import Manager from multiprocessing import Process def func(dic): print(dic) if __name__ == '__main__': m = Manager() dic = m.dict({"count": 100}) p = Process(target=func, args=(dic,)) p.start() # 这里子进程打印:<DictProxy object, typeid 'dict' at 0x1e50aa2f040; '__str__()' failed> from multiprocessing import Manager from multiprocessing import Process def func(dic): print(dic) if __name__ == '__main__': m = Manager() dic = m.dict({"count": 100}) p = Process(target=func, args=(dic,)) p.start() p.join() # 这里加了p.join后子进程打印:{'count': 100}
所以想要使用manager模块共享数据的,必须在开启子进程之后进行一次join,join就是主进程等待子进程结束
开启一次进程之后执行join
from multiprocessing import Manager from multiprocessing import Process def func(dic): dic["count"] -= 1 print(dic) if __name__ == '__main__': m = Manager() d = {"count": 100} dic = m.dict({"count": 100}) # 进程间共享的数据传给每个进程让每个进程去使用 for i in range(100): p = Process(target=func, args=(dic,)) p.start() p.join() # 子进程依次打印{'count': 99}到{'count': 0} # 上面这样变成一个同步的了,100个进程需要每开启一个进程等待进程结束然后再开启一个进程再运行,100个进程依次运行的 # 使用manager创建的dict现在每个进程之间可以共享数据dic了,一个进程中对dic进行修改再另外的进程中感知到了 # 如果使用普通的dict那么dict数据不会共享的 from multiprocessing import Manager from multiprocessing import Process def func(dic): dic["count"] -= 1 print(dic) if __name__ == '__main__': m = Manager() d = {"count": 100} dic = m.dict({"count": 100}) l = [] for i in range(100): p = Process(target=func, args=(dic,)) l.append(p) p.start() [p.join() for p in l] print(dic) 这样在for循环外面统一join让100个进程之间执行是异步的,100个进程共享dic字典,统一去起进程,统一主进程等待结束,
都结束了说明100个进程执行完了,最后主进程打印dic应该是0,但是也可能不是0,
因为dic被所有进程共享,几个进程同时取数据dic,几个进程同时拿到100去运算同时-1,几个进程都算出是99,多个进程可能算重了,因为是异步
Manager提供了很多共享数据的机制:dict,list,pipe,但是并不提供数据安全的支持,如果想让数据安全需要自己加锁
如下manager数据操作加锁的代码,让并发数据更加安全
from multiprocessing import Manager
from multiprocessing import Process
from multiprocessing import Lock
def func(dic, lock):
lock.acquire()
dic["count"] -= 1
lock.release()
if __name__ == '__main__':
m = Manager()
lock = Lock()
d = {"count": 100}
dic = m.dict({"count": 100})
l = []
for i in range(100):
p = Process(target=func, args=(dic, lock))
l.append(p)
p.start()
[p.join() for p in l]
print(dic)
一般不自己这样实现,还是使用queue队列更加安全
17:进程池 Pool 参考文档:https://www.cnblogs.com/Eva-J/articles/8253549.html
Pool([numprocess [,initializer [, initargs]]]):创建进程池 参数介绍: 1 numprocess:要创建的进程数,如果省略,将默认使用cpu_count()的值 2 initializer:是每个工作进程启动时要执行的可调用对象,默认为None 3 initargs:是要传给initializer的参数组 主要方法: 1:p.apply(func [, args [, kwargs]]):
在一个池工作进程中执行func(*args,**kwargs),然后返回结果。同步的调用func任务函数
(那么在进程池中顺序执行,没有并发效果,比如进程池5个进程,就算有5个进程每次也只执行一个任务) 需要强调的是:此操作并不会在所有池工作进程中并执行func函数。如果要通过不同参数并发地执行func函数,
必须从不同线程调用p.apply()函数或者使用p.apply_async() 2:p.apply_async(func [, args [, kwargs]]):
在一个池工作进程中执行func(*args,**kwargs),然后返回结果。异步调用func函数任务
这个apply_async异步调用和主进程也异步了,和process调用进程后.start不一样,
执行到res = p.apply_async(func, args=(i,))的时候主进程后面代码没有了,那么主进程结束,但是进程池里面的进程还在,主进程结束了但是进程池还在运行
这样非常不好,内存当作可能产生一大顿没有被回收的进程
所以当我们使用进程池异步调用的时候需要如下:
1:p.close() 完成所有进程调用之后close,表示不能再往进程池中添加新的任务,join之前必须close,不close就join就会报错,
2:p.join() 阻塞等待,执行进程池中的所有任务直到执行结束,主进程在这里等待进程池结束
只有在异步调用的时候才需要close和join,同步调用本身就顺序执行,不需要做这个事情
此方法的结果是AsyncResult类的实例,callback是可调用对象,接收输入参数。当func的结果变为可用时,
将理解传递给callback。callback禁止执行任何阻塞操作,否则将接收其他异步操作中的结果 3:p.close():
关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成 4:P.jion():
等待所有工作进程退出。此方法只能在close()或teminate()之后调用 其他方法(了解): 1:方法apply_async()和map_async()的返回值是AsyncResul的实例obj。实例具有以下方法 2:obj.get():返回结果,如果有必要则等待结果到达。timeout是可选的。如果在指定时间内还没有到达,将引发一场。如果远程操作中引发了异常,它将在调用此方法时再次被引发。 3:obj.ready():如果调用完成,返回True 4:obj.successful():如果调用完成且没有引发异常,返回True,如果在结果就绪之前调用此方法,引发异常 5:obj.wait([timeout]):等待结果变为可用。 6:obj.terminate():立即终止所有工作进程,同时不执行任何清理或结束任何挂起工作。如果p被垃圾回收,将自动调用此函数
进程不能无限开辟,电脑内存限制,当开启一个进程的时候,
需要给进程分配资源(内存,寄存器,堆,栈,进程名,进程值等都存储到内存里,
存储这些值需要一个数据结构假设类似一个字典,需要创建字典,把进程的名字和值放进去,然后给进程分配一块内存空间存储字典)等等这些操作
开启进程这一步需要耗费不少的时间(创建数据结构,进程分配内存空间都耗费时间),所以进程不能无限开启,开进程的过程很耗费时间和空间的
如果需要执行100个任务,电脑是4核的,那么就同时只开4个进程,然后一个进程执行完一个任务后就释放进程,进程不关闭,进程放着给别人用
第5个任务进来了占用闲置的进程执行任务,如果4个进程又执行完了一个任务了第4个任务又进来执行
始终只有4个进程,4个进程同时运行,去轮询执行完100个任务一定比开辟100个进程执行100个任务更节省时间,
因为就算开辟100个进程也还是在多核cpu轮着执行的,开辟100个进程耗费时间,调度100个任务也耗费时间(调度100个进程和4个进程,开启100个进程和4个进程明显4个更省时间)
所以现在4核cpu——>开始4个进程——>4个进程完成100个任务 这种思想叫做进程池
造一个池造4个进程来完成工作
cpu16核也只能同时处理16个任务,进程太多也不会运行得更快,就算开160个进程,16核得,假设时间片是0.01s转一次
需要0.1s才能轮询到一次进程执行0.01s这一会,如果进程更多长到0.2或者0.5就能感知到速度了,所以进程不要无限开启
进程池为什么出现:开过多的进程
1:开启进程浪费时间
2:回收进程资源也浪费时间
3:操作系统调度太多的进程也影响效率
开进程池:
有几个现成的进程在池子里来给用户处理任务,有任务来了就用这个池子中的进程去处理任务
任务处理完了之后再把进程放回池子里,池子中的进程就能去处理其他任务了
当所有的任务都处理完了,进程池就关闭了,回收池子中所有的进程
开的进程数:
一般情况下cpu_count + 1
from multiprocessing import Pool import time import random import os def func(i): print(i, os.getpid(), "-----") time.sleep(random.randint(1, 3)) if __name__ == '__main__': # p = Pool(os.cpu_count() + 1) # 一般情况下创建的进程池大小为电脑核数+1,如上 p = Pool(5) # 创建进程池.进程池5个进程 p.map(func, range(20)) p.close() # 这里不是关闭进程池,是不允许再向进程池种添加任务了,close之后不能再提交任务了,再提交就报错了 p.join() # print("_____________________") # 进程池这里5个进程去执行20个任务,并发最多5个一起并发 windows操作系统 每次都是最后打印主进程的print("_____________________") 但是其他操作系统可能("_____________________")这个先打印出来 如果想print("_____________________")主进程的这个最后打印 1:先 p.close() 2:然后p.join() 加了close和join能保证后面的代码在池的后面才执行
from multiprocessing import Pool from multiprocessing import Process import time def func(i): i += 1 if __name__ == '__main__': # 开辟进程池,5个进程,去执行完100个func任务耗费的时间 p = Pool(5) # 创建进程池.进程池5个进程 start_time = time.time() p.map(func, range(100)) # target = func,args = next(iterable) # map的第一个参数是任务函数,map的第二个参数必须是可迭代的数据类型,迭代一次就传一个值给func当作参数 # map传递多个参数:[(1,2,3), (1,2,3)]这样传递就行了 p.close() # 这里不是关闭进程池,是不允许再向进程池种添加任务了,close之后不能再提交任务了,再提交就报错了 p.join() # join执行完了这里说明所有的子进程任务都执行完了,说明子进程gg了 end_time = time.time() print(end_time - start_time) # 0.16156768798828125 # 开辟100个进程去执行完100个func任务耗费的时间 start_time = time.time() l = [] for i in range(100): p = Process(target=func, args=(i,)) p.start() l.append(p) [p.join() for p in l] end_time = time.time() print(end_time - start_time) # 2.529505491256714 进程池手段: 100个任务,4个进程去执行完这100个任务:耗费时间0.16156768798828125 100个任务,100个进程去执行:耗费时间2.529505491256714 所以同样的任务使用进程池能节省很多时间,做同样的事情,都使用进程池 开进程和进程调度很耗费时间,进程不能无限开辟,使用进程池优化代码
# 进程池:apply的使用,apply是一直同步提交任务的机制 import time from multiprocessing import Pool def func(i): time.sleep(1) i += 1 print(i) if __name__ == '__main__': p = Pool(5) for i in range(20): p.apply(func, args=(i, )) 5个进程池,20个任务,但是每隔1s才打印一个数据,执行速度很慢,这里没有5个5个并发执行任务
因为apply是一直同步提交任务的机制,
# 进程池:apply_async的使用,apply_async是异步提交任务的机制 import time from multiprocessing import Pool def func(i): time.sleep(1) i += 1 print(i) if __name__ == '__main__': p = Pool(5) for i in range(20): p.apply_async(func, args=(i, )) p.close() p.join()
# 主进程等待子进程结束再往下执行,没有这两行代码主进程不等子进程结束才结束,
这和前面的Process开辟的进程不一样,前面开辟的子进程,主进程需要等待子进程结束才结束,进程池开启后执行任务主进程不等子进程了 ----所以这就是一个问题
所以主进程必须join等待子进程结束,join之前必须close()不允许再提交新的任务了-----异步提交任务
apply_async提交任务,异步提交机制,func任务执行的时候5个并发一起执行,
apply_async就和提交process然后start形式一样,异步提交
多进程执行任务的返回值: 多进程不能任务函数不能返回值,但是能通过队列主进程拿到子进程的执行结果,
子进程执行完之后把结果放到队列里,然后主进程或者其他进程队列取值就行了 进程池的结果返回值也是基于管道或者队列实现的 import time from multiprocessing import Pool def func(i): time.sleep(1) i += 1 return i+1 if __name__ == '__main__': p = Pool(5) res_l = [] for i in range(20): res = p.apply_async(func, args=(i,)) print(res.get()) # 阻塞,等待任务结果 p.close() p.join() 这样res.get取进程执行结果现在20个任务执行又变成同步的了, 每次apply_async提交一次任务拿到一个结果对象,对象.get()就能拿到结果了, apply_async提交一个任务后马上又要get()得到任务执行的结果,只能等着任务执行完成后拿到执行结果才会解除res.get()
这里的阻塞,res.get()这里一直是阻塞等待任务执行的结果,结果返回就不会阻塞了, 所以上面的代码for循环里一直一个任务执行的时候主进程等待结果到手之后才for循环下一次开启下一个任务执行又拿结果,所以就变成同步执行了 # 进程池异步提交任务异步执行拿到结果的代码 # 不直接get拿结果,只把存储了结果的结果对象放在列表里,等到主进程join等到所有子进程任务执行完了之后才get拿结果 # 等进程执行完了之后才统一拿结果,又能实现异步并发了,且还能拿到程序执行的结果 import time from multiprocessing import Pool def func(i): time.sleep(1) i += 1 return i+1 if __name__ == '__main__': p = Pool(5) res_l = [] for i in range(20): res = p.apply_async(func, args=(i,)) res_l.append(res) p.close() p.join() [print(i.get()) for i in res_l]
# apply:同步调用任务的返回对象没有get方法,直接就能拿到值 import time from multiprocessing import Pool def func(i): time.sleep(1) i += 1 return i if __name__ == '__main__': p = Pool(5) res_l = [] for i in range(20): res = p.apply(func, args=(i,)) print(res) 打印:1-20
上面apply同步往进程池提交任务后直接返回结果,不需要使用get方法
同步执行本来就算顺序调用,直接拿到结果了
# apply_async:异步调用任务获取任务函数的返回值得到的是一个res结果对象,需要对结果对象调用get才能取到值 import time from multiprocessing import Pool def func(i): time.sleep(1) i += 1 return i if __name__ == '__main__': p = Pool(5) res_l = [] for i in range(20): res = p.apply_async(func, args=(i,)) res_l.append(res) p.close() p.join() [print(i.get()) for i in res_l]
# map:提交任务,异步的 import time from multiprocessing import Pool def func(i): time.sleep(1) i += 1 return i if __name__ == '__main__': p = Pool(4) ret = p.map(func, range(10)) print(ret) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 每一次提交一个任务:func(next(range(10))) 每一次提交func任务然后把range(10)循环传参, range(10)能迭代多少次,前面的func就被提交多少次.每一次func函数提交到进程里面去执行的, func任务执行的参数就从range迭代取值 本质就是 起一个进程执行func(0) 又起一个进程执行func(1) 又起一个进程执行func(2) ... 一直拿进程来执行,到进程池中没有资源了就等着谁执行完了就把结果给我,再往里提交任务 拿到的ret结果就算20个任务全部执行完的结果,拿到直接打印就行 20个任务都是异步执行的,但是主进程在等着20个任务执行完拿到结果才结束 map提交任务传参第2个参数必须是可迭代类型的数据
进程池中的回调函数 callback
import time import os from multiprocessing import Pool def func(i): # 如果多进程中的io多,如果同步调用每次都需要这里等很久, # 如果起多进程处理io就变成好多个人在这里等着数据来,谁数据先来了就在我自己的进程里先取, # 不开多进程只能在我自己的进程里起一个进程等着,等的过程1个人在这里等,这1条数据不回来就一直等 # 如果开多个进程,多个进程都在等数据,谁拿回数据来,多个进程拿回数据的几率大,一个进程的话只能死等着 # 开一个进程所有的使用都用来等待了, # 开多个进程就可以让多个进程谁拿到数据就给我的主程序返回回来然后主进程处理就行了 print(f"子进程:{i}:{os.getpid()}") return i * "*" def call(arg): print(f"回调:{os.getpid()}") print(arg) if __name__ == '__main__': print(f"主进程:{os.getpid()}") p = Pool(5) for i in range(10): p.apply_async(func, args=(i, ), callback=call) p.close() p.join() 起一个多进程池 1:调用func函数,执行完func有一个返回值,设置了callback之后返回值就不返回了 把返回值直接传给callback直到的函数的参数了,然后callback回调函数继续执行 回调函数: 开启进程之后执行了一段代码有个结果,这个结果直接传给另外一个函数让他去做进一步的处理 回调函数是在主进程中完成的,回调函数只能接收多进程中任务函数的返回值,不能传递参数----回调函数的特点
进程池中回调函数的使用: 请求网页: 下载页面,网络延时,io操作有等待的事情 单进程请求页面: 请求10个页面,同步去取,发起一个请求,服务端数据返回,然后我去做处理,整体一条直线下来的 一个个页面访问请求,很耗费时间,在请求网络上网页等待网页返回数据的过程耗时最大, 所以起多个进程同时去访问多个网页 进程池5个进程,同时发了5个请求给所有网页之后还是在这里等,不同的在于等1s就表示给5个进程都等了1s 等1s之后可能陆陆续续就有值返回回来,谁先返回回来就先处理,所有io等待的时间缩减了 ---使用回调函数实现 10个网页 同时访问多个 多进程 分析页面 回调函数 from multiprocessing import Pool import requests def get_url(url): ret = requests.get(url) ret.encoding = "utt8" return {"url": url, "status_code": ret.status_code, "content": ret.text } def parser(dic): print(dic['url'], dic["status_code"], len(dic["content"])) if __name__ == '__main__': p = Pool(4) url_l = ["https://www.baidu.com/", "https://www.sogou.com/", "https://www.hao123.com/", "https://www.jd.com/", "https://www.taobao.com/"] for url in url_l: p.apply_async(get_url, args=(url,), callback=parser) p.close() p.join()
回调函数多数用在爬虫的地方,等待多个网页的时候等待时间比较长,多进程缩短等待时间,主进程分析新来的数据就行了
网页得到数据一般不会完全同时的,尤其访问多个网页的时候,不会同时的,很有可能按照顺序来的,中间会有一些时间间隔的
时间隔离里足够处理网页了数据分析了
java里:
创建进程池的时候,给一个最大的数,进程池里最多放几个进程,假设放10个
先来了一个任务就开一个进程,来两个任务再开一个进程,来5个任务开5个进程
并发20个任务最多也就开10个进程,
正常情况下情况都在进程池里,最多10个进程,老是2个2个任务提交给进程,那么10个进程有8个是浪费的
一段时间不用时候就把其余8个进程回收了-----java里的机制
java里进程池里进程的数量是根据任务的数量动态变化的
python里进程池开4个进程就一直放这里----所有不要瞎几把开,浪费内存资源