Pipe(管道),Event(事件),Semaphore(信号量),Pool(进程池),回调函数
一、关于Pipe(管道)队列就是基于管道的方法,不常用,因为管道中的内容是共享的,数据不安全,而且一个数据取走后,其他人没法接收.
由Pipe方法返回的两个连接对象表示管道的两端。每个连接对象都有send和recv方法(除其他之外)。注意,如果两个进程(或线程)试图同时从管道的同一端读取或写入数据,那么管道中的数据可能会损坏。当然,在使用管道的不同端部的过程中不存在损坏风险。
应该特别注意管道端点的正确管理问题。如果是生产者或消费者中都没有使用管道的某个端点,就应将它关闭。这也说明了为何在生产者中关闭了管道的输出端,在消费者中关闭管道的输入端。
如果忘记执行这些步骤,程序可能在消费者中的recv()操作上挂起(就是阻塞)。管道是由操作系统进行引用计数的,必须在所有进程中关闭管道的相同一端就会能生成EOFError异常。
因此,在生产者中关闭管道不会有任何效果,除非消费者也关闭了相同的管道端点。
我们的目的就是关闭所有的管道,那么主进程和子进程进行通信的时候,可以给子进程传管道的一端就够了,并且用我们之前学到的,信息发送完之后,再发送一个结束信号None,
那么你收到的消息为None的时候直接结束接收或者说结束循环,就不用每次都关闭各个进程中的管道了。
from multiprocessing import Process,Pipe def f1(conn): from_zhujincheng = conn.recv() print('我是子进程') print('来自主进程的消息:',from_zhujincheng) if __name__ == '__main__': conn1,conn2 = Pipe() #创建一个管道对象,全双工,返回管道的两端,但是一端发送的消息,只能另外一端接收,自己这一端是不能接收的 #可以将一端或者两端发送给其他的进程,那么多个进程之间就可以通过这一个管道进行通信了 p1 = Process(target=f1,args=(conn2,)) p1.start() conn1.send('小宝贝,你在哪') print('我是主进程')
二、关Event() (简称事件)
什么是事件:
用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。
事件的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
只应用于2个进程之间,因为进程多了,该机制方法不成立
方法介绍:
e.clear:将“Flag”设置为False
e.set:将“Flag”设置为True
from multiprocessing import Process,Event e = Event() #创建事件对象,这个对象的初识状态为False print('e的状态是:',e.is_set()) print('进程运行到这里了') e.set() #将e的状态改为True print('e的状态是:',e.is_set()) e.clear() #将e的状态改为False e.wait() #e这个事件对象如果值为False,就在我加wait的地方等待 print('进程过了wait')
三、Semaphore()方法(简称信号量)
什么是信号量: 互斥锁同时只允许一个线程更改数据,而信号量Semaphore是同时允许一定数量的线程更改数据 。信号量同步机制适用于访问像服务器这样的有限资源,默认是1个进程
参数介绍:
s = Semaphore(4) #实例化信号量,为4个
s.acquire() #加锁 (百度翻译为:获得)
s.release() #解锁 (百度翻译为:释放,解除)
信号量的使用:
from multiprocessing import Process,Semaphore import time,random def go_ktv(sem,user): sem.acquire() #加锁,信号量没有with方法 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): #参数1是子进程,参2传进去信号量和正常所需参数 p=Process(target=go_ktv,args=(sem,'user%s' %i,)) p.start() #下指令,开启进程 p_l.append(p) #将这个进程名放进列表,方便后面统一阻塞 for i in p_l: i.join() print('============》')
四、关于进程池
什么是进程池:
定义一个池子,在里面放上固定数量的进程,有需求来了,就拿一个池中的进程来处理任务,等到处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务。
如果有很多任务需要执行,池中的进程数量不够,任务就要等待之前的进程执行任务完毕归来,拿到空闲进程才能继续执行。也就是说,池中进程的数量是固定的,那么同一时间最多有固定数量的进程在运行。
特点是:这样不会增加操作系统的调度难度,还节省了开闭进程的时间,也一定程度上能够实现并发效果
参数介绍:
p.apply(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。 p.apply_async(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。 p.close():关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成 P.jion():等待所有工作进程退出。此方法只能在close()或teminate()之后调用
p.map(obj,inter):快速生成n项(可迭代项)任务,交给进程池,属于异步提交方式,自带close,join属性
使用进程池运算和创建进程运算,时间的对比
import time from multiprocessing import Process,Pool def f1(n): for i in range(5): n = n+i if __name__ == '__main__': #使用进程池的执行代码所需时间 s_time = time.time() pool=Pool(4) #有100个任务,但是只有4个进程能执行. pool.map(f1,range(100)) #参数1为开启进程的对象,参数2必须是可迭代的,属于异步提交方式 e_time = time.time() dif_time = e_time - s_time #使用多进程执行代码所需的时间 p_s_t = time.time() #起始时间 p_list = [] for i in range(100): p = Process(target=f1,args=(i,)) p.start() p_list.append(p) [pp.join() for pp in p_list] p_e_t = time.time() p_dif_t = p_e_t - p_s_t print("进程池的时间:",dif_time) print("多进程的执行时间:",p_dif_t) #打印结果为:每次结果都不一致,但是进程池用比多进程用的时间短,是一定的. #进程池的时间: 0.1326451301574707 #多进程的执行时间: 2.274912118911743
res = pool.apply(obj,args=(i,)) 进程池的同步提交方式,必须等任务执行结束才能给进程池提交下一个任务,可以直接拿到返回结果res
#进程池的同步异步方法 import time from multiprocessing import Pool def f1(n): time.sleep(0.5) return n*n if __name__ == '__main__': pool = Pool(4) for i in range(10): res = pool.apply(f1,args=(i,)) #同步方法,就是把它该成串行,可以接收返回值 print(res)
pool.apply_async(obj,args(i,)) #进程池的异步提交方式
import time from multiprocessing import Process,Pool def f1(n): time.sleep(0.5) return n*n if __name__ == '__main__': pool = Pool(4) res_list = [] for i in range(10): res = pool.apply_async(f1,args=(i,)) print(res) #这里打印的就是一堆结果集(一堆内存地址) res_list.append(res) for i in res_list: print(i.get()) #get() 获取返回值 print("主进程结束") #解释:为什么打印出来的结果是有序的? # 因为放进列表里面的时候,是有顺序的,你get()的时候,用的列表里面的数据 # 为什么结果是四个四个的出来? # 因为进程池的大小为4
一次性获取到所有进程执行完后的数据
关于进程池的回调函数
什么是回调函数:进程池中任何一个任务一旦处理完了,就立即告知主进程:我好了额,你可以处理我的结果了。主进程则调用一个函数去处理该结果,该函数即回调函数.我们可以把耗时间(阻塞)的任务放到进程池中,
然后指定回调函数(主进程负责执行),这样主进程在执行回调函数时就省去了I/O的过程,直接拿到的是任务的结果
回调函数是在主进程里面执行的.
回调函数在写的时候注意一点,回调函数的形参执行有一个,如果你的执行函数有多个返回值,那么也可以被回调函数的这一个形参接收,接收的是一个元祖,包含着你执行函数的所有返回值。
import os from multiprocessing import Pool,Process def f1(n): print("参数值为",n) return n*n def call_back_full(s): print("call进程id",os.getpid()) print("回调函数的结果:",s) if __name__ == '__main__': pool = Pool(4) res = pool.apply_async(f1,args=(5,),callback=call_back_full) #callback 译为,回调 pool.close() #如果想让主程序等待进程池,必须把进程池锁住,pool.close(),在pool.join不然会报错 pool.join() print("主进程id",os.getpid())