进程间的控制:锁Lock,信号量Semaphore,事件Event
进程间的数据通信:队列(进程安全的)和管道(进程不安全的)
进程间的通信
用文件共享数据可以实现进程间通信,但也有如下的缺点:
1.效率低(共享数据基于文件,而文件是硬盘上的数据)
2.需要自己加锁处理
进程异步是为了提高效率,但是为了数据安全加锁后,将之改为同步
又降低的效率
因此我们需要一种解决方案能够兼顾:
1、效率高(多个进程共享一块内存的数据)
2、数据安全问题的处理。
这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道
队列和管道都是将数据存放于内存中
队列又是基于(管道+锁)实现的
所以应该尽量避免使用共享数据,尽可能使用消息传递和队列,
避免处理复杂的同步和锁问题
队列
from multiprocessing import Queue
可以进行进程间的通信,是线程安全的
队列是先进先出的数据类型
创建:
Queue([maxsize])
maxsize是队列中允许的最大长度。如果省略此参数,则无大小限制
put(index)添加数据,put的index值大于队列长度会发生阻塞现象
get()获取数据,当队列已经为空,会发生阻塞现象
full()查看队列是否已经满了,返回 (空)True/(非空)False, 多进程下不准确
empty()判断队列是否空,多进程下不准确
get_nowait()获取值,如果队列没有值不会阻塞而是抛出queue empty异常
qsize()返回队列中目前队列中的元素数量,多进程下不准确
from multiprocessing import Process,Queue #这个可以进行进程间的通信
#进程间的通信
def put(q):
q.put('哈哈哈')
def get(q):
if not q.empty():
print(q.get())
if __name__ == '__main__':
q = Queue()#创建队列
p = Process(target = put,args=(q,))#创建进程,传入参数
p.start()#启动进程
p2 = Process(target = get,args=(q,))#创建进程,传入参数
p2.start()#启动进程
#两个进程间的队列必须是同一个队列对象
管道
from multiprocessing import Pipe
可以进行进程间的通信,是非线程安全的
from multiprocessing import Pipe
#管道
conn1,conn2 = Pipe()#创建一个管道并返回管道两端的连接
#print(conn2.recv()) #如果管道没有数据而直接去接收会发生阻塞
conn1.send('123') #在端口1发送字符串
print(conn2.recv())#在端口2可以接收字符串
conn2.send('456')#在端口2发送字符串
print(conn1.recv())#可以反向发送数据
管道的循环取值
from multiprocessing import Pipe,Process
#结束循环中的管道
#import time
def fun(c1,c2):
c2.close()
while True:
try:
s = c1.recv()#向管道中获取数据
print(s)
except EOFError:#除了当前接收数据的连接c1外,管道其它的所有连接都关闭且管道无数据时,会触发EOFError
c1.close()
break
if __name__ == '__main__':
c1,c2 = Pipe()
Process(target = fun,args = (c1,c2)).start()#子进程与主进程的连接是不会相互影响的
c1.close()
for i in range(20):
c2.send(i)#向管道中存放数据
c2.close()
其他通信方式
from multiprocessing import Manager进程间通信模块
需要加锁控制数据的安全
from multiprocessing import Process,Manager,Lock
import time
def fun(dic,l):
l.acquire()#加锁
time.sleep(0.04)
dic['count'] -= 1
l.release()#释放锁
#print('子进程',dic['count'])
if __name__ == '__main__':
m = Manager() #进程间通信模块
d = m.dict({'count':100}) #创建具有共享属性的字典,但是不安全
locks = Lock() #需要加锁来控制进程间的通信
l = []
for i in range(30):
p = Process(target = fun,args = (d,locks))
p.start()
l.append(p)
for j in l :
j.join()
print('主进程',d['count'])
生产者/消费者模式
生产者生产数据过快,消费者处理数据太慢?
可以设置一个容器(队列)存放生产数据,用容器的大小限制生产速度,容器满了就不在生产
生产者生产数据过慢,消费者处理数据过快?
多增加几个生产源
如何判断生产者生成数据完毕?
a.判断队列是否为空,判断生成代码是否执行完毕
但是q.empty不适用于多进程护环境
b.队列现在为空,可能生产者还未来得及生产,不一定生产者代码执行完毕
所以不能以队列是否为空判断生产者执行完毕
在生产者结束后再次向队列添加一个标记数据,消费者获取到标记数据就关闭进程
但是在多进程的多个消费模式下,获取到标记数据的进程会关闭,未获取到标记数据的会继续阻塞
所以有多少个消费者就添加多少个标记数据
import time
import random
from multiprocessing import Queue,Process
def consumer(name,q):
while True:
s = q.get()
if( s== 1):
break
time.sleep(random.randint(1,3))
print('%s消费了%s'%(name,s))
def producer(name,food,q):
for i in range(10):
s = '%s产生第%d个%s'%(name,i,food)
print(s)
q.put(s)
time.sleep(random.randint(1,3))
if __name__ == '__main__':
q = Queue(20)#新建队列,容量为20——数据缓冲区
p1 = Process(target = producer,args = ('主机1','数据1',q))
p1.start()#启动生产者p1进程
p2 = Process(target = producer,args = ('主机2','数据2',q))
p2.start()#启动生产者p2进程
c1 = Process(target = consumer,args = ('主机3',q))
c1.start()#启动消费者c1进程
c2 = Process(target = consumer,args = ('主机4',q))
c2.start()#启动消费者c2进程
p1.join()#感知生成者p1进程的结束
p2.join()#感知生成者p2进程的结束
q.put(1)#标记数据
q.put(1)#标记数据
改进
from multiprocessing import JoinableQueue
JoinableQueue模块比Queue模块多了两个方法,
在q.get数据后要提交一个回执q.task_done(),标记值-1,操作成功
队列每添加q.put()一个数据就自动增加一个标记值
q.join() 生产者代码执行结束后,阻塞当前代码直到消费者将数据全部处理完毕
import time
import random
from multiprocessing import JoinableQueue,Process
def consumer(name,q):
while True:
s = q.get()
time.sleep(random.randint(1,3))
print('%s消费了%s'%(name,s))
q.task_done()
def producer(name,food,q):
for i in range(10):
s = '%s生产了第%d个%s'%(name,i,food)
print(s)
q.put(s)
time.sleep(random.randint(1,3))
q.join() #阻塞,直到队列中所有数据全被处理完毕(消费者消费完毕)
if __name__ == '__main__':
q = JoinableQueue(20)
p1 = Process(target = producer,args = ('食堂阿姨','包子',q))
p1.start()
p2 = Process(target = producer,args = ('食堂大妈','馒头',q))
p2.start()
c1 = Process(target = consumer,args = ('学生1',q))
c2 = Process(target = consumer,args = ('学生2',q))
c1.daemon = True #将消费者设为守护进程——等待主进程代码执行完毕
c2.daemon = True #将消费者设为守护进程——等待主进程代码执行完毕
c1.start() #启动消费者进程
c2.start() #启动消费者进程
p1.join() #感知生产者进程的结束——阻塞当前代码直到生产者生成完毕
p2.join() #感知生产者进程的结束
#执行流程
#所有生产者代码执行结束后,队列.join()阻塞生产者,等待消费者处理完
#消费者处理完数据,阻塞消失,生产者结束
#生产者结束,生产者进程.join()结束,主进程的代码执行完毕,守护线程(消费者结束)结束,然后主进程真正的结束
进程池
from multiprocessing import Pool
进程的创建和释放是最消耗系统资源的
过多的进程也会影响系统的调度
python中的进程池 先创建一个指定进程数量的容器称为进程池
其它应用需要进程时直接进程池中获取
进程池中进程数不够时等待其它进程将进程释放回进程池
高级进程池可以分析用户使用程序的低谷期和高峰期,设置进程池的最小进程数和最大进程数
示例一
from multiprocessing import Pool
import os
import time
def fun(dic):
for i in range(10):
dic += i
print(dic)
if __name__ == '__main__':
t1 = time.time()
pool = Pool(5) #在进程池中开启5个进程,不写默认开启CPU个数的进程
pool.map(fun,range(20))#在进程池调用fun方法,开启20个任务(传入一个可迭代参数)
#map方法,参数1函数名,参数2可迭代参数作为函数的实参
#默认异步的执行任务,自带close(不在接收任务)与join(感知所有子进程的结束)
#等价于 for i in range(20):
# p = Process(target = fun ,args =(i,))
print(time.time() - t1)#进程池提高了程序的效率
示例二
from multiprocessing import Pool
import time
def fun(dic):
for i in range(10):
dic += i
print(dic)
def fun2(dic):
print(dic[0])
print(dic[1])
if __name__ == '__main__':
t1 = time.time()
pool = Pool(5) #在进程池中开启5个进程
pool.map(fun,range(20))
pool.map(fun2,[('key',1),('value',2)])#利用map启动2个任务
#map的第二个只能传递可迭代参数,每次传入可迭代参数的一个元素,如果要一次性传入多个参数,可以将元组作为列表元素来传递
print(time.time() - t1)
示例三:进程池的同步调用
from multiprocessing import Pool
import time
import os
def fun(s):
print('start %s'%s,os.getpid())
time.sleep(0.01)
print('end %s'%s,os.getpid())
#一个进程的start接着end,表示这是一个同步程序
if __name__ == '__main__':
pool = Pool(5) #在进程池中开启5个进程
for i in range(20):
pool.apply(fun,args = (i,))#同步提交,可以有返回值
#需要与close与join同时使用
pool.close()#进程池不在接收任务
pool.join()#感知进程池中所有进程的任务执行结束
#start 0 7496
#end 0 7496
#start 1 7212
#end 1 7212
示例四:进程池的异步调用
from multiprocessing import Pool
import time
import os
def fun(s):
print('start %s'%s,os.getpid())
time.sleep(0.01)
print('end %s'%s,os.getpid()
if __name__ == '__main__':
pool = Pool(5) #在进程池中开启5个进程
for i in range(20):
pool.apply_async(fun,args = (i,))#真异步,不会等待子进程结束,需要手动的close和join
#需要与close与join同时使用
pool.close()#进程池不在接收任务
pool.join()#感知进程池中所有进程的任务执行结束
# start 0 7500
#start 1 4016
#start 2 3748
#end 0 7500
进程池的返回值
p = Pool()创建进程池
p.map(functionName,iterable) 默认异步的执行任务,自带close与join
p.apply() 默认同步执行任务,可以有返回值
p.apply_async() 默认异步执行任务,和主进程完全异步,
需要手动的close和join,返回值需要get()获取,会阻塞当前进程,等待子进程运行完毕
from multiprocessing import Pool
import time
def fun(i):
time.sleep(0.5)
return i * i
if __name__ == '__main__':
p = Pool(5)
ret = p.map(fun,range(10)) #一次性返回所有返回值,如果进程任务过多时,apply_async()更好
print(ret)#获取返回值
#[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
ret = p.apply(fun,args=(i,))
print(ret)#获取返回值
异步返回值的获取
from multiprocessing import Pool
import time
def fun(i):
time.sleep(0.5)
return i * i
if __name__ == '__main__':
p = Pool(5)
l = []
for i in range(10):
ret = p.apply_async(fun,args=(i,))
#print(ret.get()) #阻塞当前进程,等待子进程运行完毕
#阻塞当前进程等待子进程1运行完毕获得ret1,用过ret1获取get值
#阻塞当前进程等待子进程2运行完毕获得ret2,用过ret2获取get值
#...
ret = p.apply_async(fun,args=(i,)) #改进
l.append(ret)
p.close()
p.join()
for i in l:
print(i.get())
回调函数
回调函数 ,常用于爬虫爬取数据后进行数据处理
爬取数据最耗时的过程(主要是网络延迟和数据下载)可以使用多进程来处理
爬取数据后就可以用回调函数处理数据,释放的多进程资源继续去爬取数据
回调函数是在主进程中调用的
import os
from multiprocessing import Pool
def func1(n):
print('func1进程',os.getpid())
return n*n
def func2(nn):
print('func2进程',os.getpid())
print('func2数据',nn)
if __name__ == '__main__':
print('主进程 :',os.getpid())
p = Pool(5)
for i in range(10):
p.apply_async(func1,args=(10,),callback=func2)
#先执行func1函数,将这个函数的返回值作为fun2函数的参数,fun1在子进程中执行,fun2在主进程中执行
p.close()
p.join()