并发编程
Posted Worth Waiting For
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发编程相关的知识,希望对你有一定的参考价值。
守护进程
什么是守护进程:
守护进程是进程的方式之一 守护进程是由主进程创建而来的
守护进程(太监)会在主进程(帝王)代码执行结束后就结束(不管守护进程运行到哪里都直接结束)
一个主进程可以启动多个守护进程 但是主进程必须一直处于运行状态 如果主进程结束的话 那么守护进程随之结束
程序会等待主进程(线程)执行完毕 但是不会等待守护进程(线程)
守护进程内无法再开启子进程 否则会抛出异常:AssertionError: daemonic processes are not allowed to have children
创建守护进程
from multiprocessing import Process import time import random class Piao(Process): def __init__(self,name): self.name=name super().__init__() def run(self): print(‘%s is piaoing‘ %self.name) time.sleep(random.randrange(1,3)) print(‘%s is piao end‘ %self.name) p=Piao(‘egon‘) p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行 p.start() print(‘主‘) daemon=True 守护进程的创建
#主进程代码运行完毕,守护进程就会结束 from multiprocessing import Process from threading import Thread import time def foo(): print(123) time.sleep(1) print("end123") def bar(): print(456) time.sleep(3) print("end456") p1=Process(target=foo) p2=Process(target=bar) p1.daemon=True p1.start() p2.start() print("main-------") #打印该行则主进程代码结束,则守护进程p1应该被终止,可能会有p1任务执行的打印信息123,因为主进程打印main----时,p1也执行了,但是随即被终止
进程锁/互斥锁
什么是进程锁:
同步锁或者叫互斥锁
互斥锁的作用:
保护进程的数据安全 解决多个进程竞争带来的错乱问题使得竞争有序化
ps:加锁相当于上厕所,大家都能看到那个厕所,但是里面的人上锁不出来后面的就无法运行,必须等他运行结束才可以,这就保证了有序化,但是效率慢
锁(Lock) from multiprocessing import Process, Lock def ChildProcess(l, i): l.acquire() # 获取锁 print(‘hello world‘, i) l.release() # 释放锁 if __name__ == ‘__main__‘: lock = Lock() # 生成Lock对象 for num in range(10): Process(target=ChildProcess, args=(lock, num)).start() # 创建并启动一个子进程
进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,
part1 多个进程共享同一个打印终端
#并发运行,效率高,但竞争同一打印终端,带来了打印错乱 from multiprocessing import Process import os,time def work(): print(‘%s is running‘ %os.getpid()) time.sleep(2) print(‘%s is done‘ %os.getpid()) if __name__ == ‘__main__‘: for i in range(3): p=Process(target=work) p.start()
#由并发变成了串行,牺牲了运行效率,但避免了竞争 from multiprocessing import Process,Lock import os,time def work(lock): lock.acquire() print(‘%s is running‘ %os.getpid()) time.sleep(2) print(‘%s is done‘ %os.getpid()) lock.release() if __name__ == ‘__main__‘: lock=Lock() for i in range(3): p=Process(target=work,args=(lock,)) p.start()
part2 多个进程共享同一个文件(示例:文件充当数据库 模拟抢票)
#文件db的内容为:{"count":1} #注意一定要用双引号,不然json无法识别 def search(): dic=json.load(open(‘db.txt‘)) print(‘\033[1;31;40m剩余票数%s\033[0m‘%dic[‘count‘]) def get(): dic=json.load(open(‘db.txt‘)) time.sleep(0.1)#模拟读数据的网络延迟 if dic[‘count‘]>0: dic[‘count‘]-=1 time.sleep(0.2)#模拟写数据的网络延迟 json.dump(dic,open(‘db.txt‘,‘w‘)) print(‘\033[1;31;40m购票成功\033[0m‘) def task(lock): search() get() if __name__==‘__main__‘: lock=Lock() for i in range(5):#模拟并发5个客户端抢票 p=Process(target=task,args=(lock,)) p.start() 剩余票数1 剩余票数1 剩余票数1 剩余票数1 剩余票数1 购票成功 购票成功 购票成功 购票成功 购票成功
def search(): dic=json.load(open(‘db.txt‘)) print(‘\033[1;31;40m剩余票数%s\033[0m‘%dic[‘count‘]) def get(): dic=json.load(open(‘db.txt‘)) time.sleep(0.1) if dic[‘count‘]>0: dic[‘count‘]-=1 time.sleep(0.2) json.dump(dic,open(‘db.txt‘,‘w‘)) print(‘\033[1;31;40m购票成功\033[0m‘) def task(lock): search() lock.acquire()#上锁 get()#买票 lock.release()#解锁 if __name__==‘__main__‘: lock=Lock() for i in range(5): p=Process(target=task,args=(lock,)) p.start()
互斥锁小结
#加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。 虽然可以用文件共享数据实现进程间通信,但问题是: 1.效率低(共享数据基于文件,而文件是硬盘上的数据) 2.需要自己加锁处理 #因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。 队列和管道都是将数据存放于内存中 队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来, 我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。
队列
进程彼此之间互相隔离,要实现进程间通信(IPC机制),使用multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的 重点是都列形式
进程间通信 默认情况下进程与进程之间是不可以互相通信的,若要实现互相通信则需要一个中间件,另个进程之间通过中间件来实现通信,下面是进程间通信的几种方式。 进程Queue # _*_ coding:utf-8 _*_ from multiprocessing import Process, Queue def ChildProcess(Q): Q.put([‘Hello‘, None, ‘World‘]) # 在Queue里面上传一个列表 if __name__ == ‘__main__‘: q = Queue() # 创建一个Queue p = Process(target=ChildProcess, args=(q,)) # 创建一个子进程,并把Queue传给子进程,相当于克隆了一份Queue p.start() # 启动子进程 print(q.get()) # 获取q中的数据 p.join()
创建队列的类(底层就是以管道和锁定的方式实现的)
Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。 参数介绍: maxsize是队列中允许最大项数,省略则无大小限制。 主要介绍: q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。 2 q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常. 4 q.get_nowait():同q.get(False) 5 q.put_nowait():同q.put(False) 7 q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。 8 q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。 9 q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样 其他方法(了解): 1 q.cancel_join_thread():不会在进程退出时自动连接后台线程。可以防止join_thread()方法阻塞 2 q.close():关闭队列,防止队列中加入更多数据。调用此方法,后台线程将继续写入那些已经入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将调用此方法。关闭队列不会在队列使用者中产生任何类型的数据结束信号或异常。例如,如果某个使用者正在被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。 3 q.join_thread():连接队列的后台线程。此方法用于在调用q.close()方法之后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread方法可以禁止这种行为
具体应用
‘‘‘ multiprocessing模块支持进程间通信的两种主要形式:管道和队列 都是基于消息传递实现的,但是队列接口 ‘‘‘ from multiprocessing import Process,Queue import time q=Queue(3) #put ,get ,put_nowait,get_nowait,full,empty q.put(3) q.put(3) q.put(3) print(q.full()) #满了 print(q.get()) print(q.get()) print(q.get()) print(q.empty()) #空了
生产者消费者模式
生产者消费者模式:
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
什么是生产者消费者模式 :
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
为什么要使用生产者和消费者模式:
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
基于队列实现生产者消费者模型
# 并发编程编程 一个负者造程序 一个负者处理程序 # 一类功能造数据 一类功能取走数据进行二次处理 进程之间数据传递 # 生产者上传数据put操作到内存 , 消费者下载get操作拿到数据 import time,random from multiprocessing import Process,Queue def producer(name,q): for i in range(10): time.sleep(random.randint(1,3)) res=‘泔水%s‘% i q.put(res) print(‘厨师%s生产了%s ‘%(name,res)) def consumer(name,q): while True: res=q.get() if res is None:break time.sleep(random.randint(1,3)) print(‘%s 吃了 %s‘%(name,res)) if __name__==‘__main__‘: q=Queue() p1=Process(target=producer,args=(‘egon‘,q)) p2=Process(target=consumer,args=(‘alex‘,q)) p1.start() p2.start() p1.join() q.put(None)
生产者消费者模型总结:
#生产者消费者模型总结 #程序中有两类角色 一类负责生产数据(生产者) 一类负责处理数据(消费者) #引入生产者消费者模型为了解决的问题是: 平衡生产者与消费者之间的速度差 #如何实现: 生产者-》队列——》消费者 #生产者消费者模型实现类程序的解耦和 此时的问题是主进程永远不会结束,原因是:生产者p在生产完后就结束了,但是消费者c在取空q之后, 则一直处在死循环中,且卡在q.get()这里
以上总结产生了一个问题:
主进程永远不会结束,原因是:生产者p在生产完后就结束了,但是消费者c在取空q之后,
则一直处在死循环中,且卡在q.get()这里
解决方式:
让生产者在生产完毕后 给队列中发一结束的信号 这样消费者在收到结束的信号后就会break出死循环
from multiprocessing import Process,Queue import time,random,os def consumer(q): while True: res=q.get() if res is None:break #收到结束信号则结束 time.sleep(random.randint(1,3)) print(‘\033[45m%s 吃 %s\033[0m‘ %(os.getpid(),res)) def producer(q): for i in range(10): time.sleep(random.randint(1,3)) res=‘包子%s‘ %i q.put(res) print(‘\033[44m%s 生产了 %s\033[0m‘ %(os.getpid(),res)) q.put(None) #发送结束信号 if __name__ == ‘__main__‘: q=Queue() #生产者们:即厨师们 p1=Process(target=producer,args=(q,)) #消费者们:即吃货们 c1=Process(target=consumer,args=(q,)) #开始 p1.start() c1.start() print(‘主‘)
注意 所谓的结束信号就是None 但是结束信号不一定要由生产者发送 主进程同样可以发送
但必须等到生产者结束后才可以发送该信号
from multiprocessing import Process,Queue import time,random,os def consumer(q): while True: res=q.get() if res is None:break #收到结束信号则结束 time.sleep(random.randint(1,3)) print(‘\033[45m%s 吃 %s\033[0m‘ %(os.getpid(),res)) def producer(q): for i in range(2): time.sleep(random.randint(1,3)) res=‘包子%s‘ %i q.put(res) print(‘\033[44m%s 生产了 %s\033[0m‘ %(os.getpid(),res)) if __name__ == ‘__main__‘: q=Queue() #生产者们:即厨师们 p1=Process(target=producer,args=(q,)) #消费者们:即吃货们 c1=Process(target=consumer,args=(q,)) #开始 p1.start() c1.start() p1.join() q.put(None) #发送结束信号 print(‘主‘)
上述的解决方案在有多个生产者和多个消费者的时候 需要用一个非常low的方法才可以解决
from multiprocessing import Process,Queue import time,random,os def consumer(q): while True: res=q.get() if res is None:break #收到结束信号则结束 time.sleep(random.randint(1,3)) print(‘\033[45m%s 吃 %s\033[0m‘ %(os.getpid(),res)) def producer(name,q): for i in range(2): time.sleep(random.randint(1,3)) res=‘%s%s‘ %(name,i) q.put(res) print(‘\033[44m%s 生产了 %s\033[0m‘ %(os.getpid(),res)) if __name__ == ‘__main__‘: q=Queue() #生产者们:即厨师们 p1=Process(target=producer,args=(‘包子‘,q)) p2=Process(target=producer,args=(‘骨头‘,q)) p3=Process(target=producer,args=(‘泔水‘,q)) #消费者们:即吃货们 c1=Process(target=consumer,args=(q,)) c2=Process(target=consumer,args=(q,)) #开始 p1.start() p2.start() p3.start() c1.start() p1.join() #必须保证生产者全部生产完毕,才应该发送结束信号 p2.join() p3.join() q.put(None) #有几个消费者就应该发送几次结束信号None q.put(None) #发送结束信号 print(‘主‘)
JoinableQueue(另外一直机制)
#JoinableQueue([maxsize]):这就像是一个Queue对象,但队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。 #参数介绍: maxsize是队列中允许最大项数,省略则无大小限制。 #方法介绍: JoinableQueue的实例p除了与Queue对象相同的方法之外还具有: q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常 q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为
# from multiprocessing import Queue,Process # import time,random # def producer(name,q,food): # for i in range(3): # time.sleep(random.randint(1,3)) # res=‘%s%s‘ %(food,i) # q.put(res) # print(‘厨师 %s 生产了 %s‘ %(name,res)) # def consumer(name,q): # while True: # res=q.get() # if res is None:break # time.sleep(random.randint(1,3)) # print(‘%s 吃了 %s‘ %(name,res)) # # if __name__ == ‘__main__‘: # q=Queue() # p1=Process(target=producer,args=(1,q,‘泔水‘)) # p2=Process(target=producer,args=(2,q,‘骨头‘)) # p3=Process(target=producer,args=(3,q,‘馒头‘)) # c1=Process(target=consumer,args=(‘alex‘,q)) # c2=Process(target=consumer,args=(‘wupeiqi‘,q)) # # p1.start() # p2.start() # p3.start() # c1.start() # c2.start() # p1.join() # p2.join() # p3.join() # q.put(None) # q.put(None) from multiprocessing import JoinableQueue,Process import time,random def producer(name,q,food): for i in range(1): time.sleep(random.randint(1,3)) res=‘%s%s‘ %(food,i) q.put(res) print(‘厨师 %s 生产了 %s‘ %(name,res)) q.join() def consumer(name,q): while True: res=q.get() if res is None:break time.sleep(random.randint(1,3)) print(‘%s 吃了 %s‘ %(name,res)) q.task_done() if __name__ == ‘__main__‘: q=JoinableQueue() p1=Process(target=producer,args=(1,q,‘泔水‘)) p2=Process(target=producer,args=(2,q,‘骨头‘)) p3=Process(target=producer,args=(3,q,‘馒头‘)) c1=Process(target=consumer,args=(‘alex‘,q)) c2=Process(target=consumer,args=(‘wupeiqi‘,q)) c1.daemon=True c2.daemon=True p1.start() p2.start() p3.start() c1.start() c2.start() p1.join() p2.join() p3.join()
from multiprocessing import Process,JoinableQueue import time,random,os def consumer(q): while True: res=q.get() time.sleep(random.randint(1,3)) print(‘\033[45m%s 吃 %s\033[0m‘ %(os.getpid(),res)) q.task_done() #向q.join()发送一次信号,证明一个数据已经被取走了 def producer(name,q): for i in range(10): time.sleep(random.randint(1,3)) res=‘%s%s‘ %(name,i) q.put(res) print(‘\033[44m%s 生产了 %s\033[0m‘ %(os.getpid(),res)) q.join() if __name__ == ‘__main__‘: q=JoinableQueue() #生产者们:即厨师们 p1=Process(target=producer,args=(‘包子‘,q)) p2=Process(target=producer,args=(‘骨头‘,q)) p3=Process(target=producer,args=(‘泔水‘,q)) #消费者们:即吃货们 c1=Process(target=consumer,args=(q,)) c2=Process(target=consumer,args=(q,)) c1.daemon=True c2.daemon=True #开始 p_l=[p1,p2,p3,c1,c2] for p in p_l: p.start() p1.join() p2.join() p3.join() print(‘主‘) #主进程等--->p1,p2,p3等---->c1,c2 #p1,p2,p3结束了,证明c1,c2肯定全都收完了p1,p2,p3发到队列的数据 #因而c1,c2也没有存在的价值了,应该随着主进程的结束而结束,所以设置成守护进程
共享数据(Managers)
from multiprocessing import Process, Manager import os def ChildProcess(Dict, List): Dict[‘k1‘] = ‘v1‘ Dict[‘k2‘] = ‘v2‘ List.append(os.getpid()) # 获取子进程的PID print(List) # 输出列表中的内容 if __name__ == ‘__main__‘: manager = Manager() # 生成Manager对象 Dict = manager.dict() # 生成一个可以在多个进程之间传递共享的字典 List = manager.list() # 生成一个字典 ProcessList = [] # 创建一个空列表,存放进程的对象,等待子进程执行用于 for i in range(10): # 生成是个子进程 p = Process(target=ChildProcess, args=(Dict, List)) # 创建一个子进程 p.start() # 启动 ProcessList.append(p) # 把子进程添加到p_list列表中 for res in ProcessList: # 循环所有的子进程 res.join() # 等待执行完毕 print(‘\n‘) print(Dict) print(List) 输出结果 [5112] [5112, 3448] [5112, 3448, 4584] [5112, 3448, 4584, 2128] [5112, 3448, 4584, 2128, 11124] [5112, 3448, 4584, 2128, 11124, 10628] [5112, 3448, 4584, 2128, 11124, 10628, 5512] [5112, 3448, 4584, 2128, 11124, 10628, 5512, 10460] [5112, 3448, 4584, 2128, 11124, 10628, 5512, 10460, 10484] [5112, 3448, 4584, 2128, 11124, 10628, 5512, 10460, 10484, 6804] {‘k1‘: ‘v1‘, ‘k2‘: ‘v2‘} [5112, 3448, 4584, 2128, 11124, 10628, 5512, 10460, 10484, 6804]
from multiprocessing import Manager,Process,Lock import os def work(d,lock): # with lock: #不加锁而操作共享的数据,肯定会出现数据错乱 d[‘count‘]-=1 if __name__ == ‘__main__‘: lock=Lock() with Manager() as m: dic=m.dict({‘count‘:100}) p_l=[] for i in range(100): p=Process(target=work,args=(dic,lock)) p_l.append(p) p.start() for p in p_l: p.join() print(dic) #{‘count‘: 94}
进程池
进程池的目的是维护固定数目的进程
创建进程池类
numprocess 指定进程池创建进程的个数
如果指定numprocess为3,则进程池会从无到有创建三个进程,然后自始至终使用这三个进程去执行所有任务,不会开启其他进程
进程池: Pool([numprocess [,initializer [, initargs]]]):创建进程池 参数介绍: 1 numprocess:要创建的进程数,如果省略,将默认使用cpu_count()的值 2 initializer:是每个工作进程启动时要执行的可调用对象,默认为None 3 initargs:是要传给initializer的参数组 方法介绍: 主要方法: 1 p.apply(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。需要强调的是:此操作并不会在所有池工作进程中并执行func函数。如果要通过不同参数并发地执行func函数,必须从不同线程调用p.apply()函数或者使用p.apply_async() 2 p.apply_async(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。此方法的结果是AsyncResult类的实例,callback是可调用对象,接收输入参数。当func的结果变为可用时,将理解传递给callback。callback禁止执行任何阻塞操作,否则将接收其他异步操作中的结果。 4 p.close():关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成 5 P.jion():等待所有工作进程退出。此方法只能在close()或teminate()之后调用 其他方法(了解部分) 方法apply_async()和map_async()的返回值是AsyncResul的实例obj。实例具有以下方法 obj.get():返回结果,如果有必要则等待结果到达。timeout是可选的。如果在指定时间内还没有到达,将引发一场。如果远程操作中引发了异常,它将在调用此方法时再次被引发。 obj.ready():如果调用完成,返回True obj.successful():如果调用完成且没有引发异常,返回True,如果在结果就绪之前调用此方法,引发异常 obj.wait([timeout]):等待结果变为可用。 obj.terminate():立即终止所有工作进程,同时不执行任何清理或结束任何挂起工作。如果p被垃圾回收,将自动调用此函数
同步调用 提交完任务后在原地等待任务结束
阻塞 正在运行的进程遇到io则进入阻塞状态
异步调用 提交完任务后不会在原地等待 会继续提交下一个任务 直到所有的任务都结束才会得到 结果
非阻塞 可能是运行态也可能是就绪态
from multiprocessing import Pool import os,time,random def work(n): print(‘%s is working‘ %os.getpid()) time.sleep(random.randint(1,3)) return n**2 if __name__ == ‘__main__‘: p=Pool(2) objs=[] for i in range(10): # 同步调用:提交完任务后,在原地等待任务结束,一旦结束可以立刻拿到结果 # res=p.apply(work,args=(i,)) # print(res) # 异步调用:提交完任务后,不会在原地等待任务结束,会继续提交下一次任务,等到所有任务都结束后,才get结果 obj=p.apply_async(work,args=(i,)) objs.append(obj) p.close() p.join() for obj in objs: print(obj.get()) print(‘主‘)
同一时间启动多少个进程 from multiprocessing import Pool import time def myFun(i): time.sleep(2) return i+100 def end_call(arg): print("end_call>>", arg) p = Pool(5) # 允许进程池内同时放入5个进程 for i in range(10): p.apply_async(func=myFun, args=(i,),callback=end_call) # # 平行执行,callback是主进程来调用 # p.apply(func=Foo) # 串行执行 print("end") p.close() p.join() # 进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
import queue import threading import contextlib import time StopEvent = object() class ThreadPool(object): def __init__(self, max_num, max_task_num = None): if max_task_num: self.q = queue.Queue(max_task_num) else: self.q = queue.Queue() self.max_num = max_num self.cancel = False self.terminal = False self.generate_list = [] self.free_list = [] def run(self, func, args, callback=None): """ 线程池执行一个任务 :param func: 任务函数 :param args: 任务函数所需参数 :param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数1、任务函数执行状态;2、任务函数返回值(默认为None,即:不执行回调函数) :return: 如果线程池已经终止,则返回True否则None """ if self.cancel: return if len(self.free_list) == 0 and len(self.generate_list) < self.max_num: self.generate_thread() w = (func, args, callback,) self.q.put(w) def generate_thread(self): """ 创建一个线程 """ t = threading.Thread(target=self.call) t.start() def call(self): """ 循环去获取任务函数并执行任务函数 """ current_thread = threading.currentThread() self.generate_list.append(current_thread) event = self.q.get() while event != StopEvent: func, arguments, callback = event try: result = func(*arguments) success = True except Exception as e: success = False result = None if callback is not None: try: callback(success, result) except Exception as e: pass with self.worker_state(self.free_list, current_thread): if self.terminal: event = StopEvent else: event = self.q.get() else: self.generate_list.remove(current_thread) def close(self): """ 执行完所有的任务后,所有线程停止 """ self.cancel = True full_size = len(self.generate_list) while full_size: self.q.put(StopEvent) full_size -= 1 def terminate(self): """ 无论是否还有任务,终止线程 """ self.terminal = True while self.generate_list: self.q.put(StopEvent) self.q.queue.clear() @contextlib.contextmanager def worker_state(self, state_list, worker_thread): """ 用于记录线程中正在等待的线程数 """ state_list.append(worker_thread) try: yield finally: state_list.remove(worker_thread) # How to use pool = ThreadPool(5) def callback(status, result): # status, execute action status # result, execute action return value pass def action(i): print(i) for i in range(30): ret = pool.run(action, (i,), callback) time.sleep(5) print(len(pool.generate_list), len(pool.free_list)) print(len(pool.generate_list), len(pool.free_list)) pool.close() pool.terminate()
同步调用/异步调用(着重于异步调用常用)
同步调用(apply) 提交完任务后 在原地等待任务结束 一旦结束可以立刻拿到结果
from multiprocessing import Pool import os,time def work(n): print(‘%s run‘ %os.getpid()) time.sleep(3) return n**2 if __name__ == ‘__main__‘: p=Pool(3) #进程池中从无到有创建三个进程,以后一直是这三个进程在执行任务 res_l=[] for i in range(10): res=p.apply(work,args=(i,)) #同步调用,直到本次任务执行完毕拿到res,等待任务work执行的过程中可能有阻塞也可能没有阻塞,但不管该任务是否存在阻塞,同步调用都会在原地等着,只是等的过程中若是任务发生了阻塞就会被夺走cpu的执行权限 res_l.append(res) print(res_l)
异步调用(apply_async) 提交完任务后不会在原地等任务结束接着提交下一次任务 直到所有的任务全部结束后才会得到结果
from multiprocessing import Pool import os,time def work(n): print(‘%s run‘ %os.getpid()) time.sleep(3) return n**2 if __name__ == ‘__main__‘: p=Pool(3) #进程池中从无到有创建三个进程,以后一直是这三个进程在执行任务 res_l=[] for i in range(10): res=p.apply_async(work,args=(i,)) #同步运行,阻塞、直到本次任务执行完毕拿到res res_l.append(res) #异步apply_async用法:如果使用异步提交的任务,主进程需要使用jion,等待进程池内任务都处理完,然后可以用get收集结果,否则,主进程结束,进程池可能还没来得及执行,也就跟着一起结束了 p.close() p.join() for res in res_l: print(res.get()) #使用get来获取apply_aync的结果,如果是apply,则没有get方法,因为apply是同步执行,立刻获取结果,也根本无需get
详解apply和apply_async
#一:使用进程池(异步调用,apply_async) #coding: utf-8 from multiprocessing import Process,Pool import time def func(msg): print( "msg:", msg) time.sleep(1) return msg if __name__ == "__main__": pool = Pool(processes = 3) res_l=[] for i in range(10): msg = "hello %d" %(i) res=pool.apply_async(func, (msg, )) #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去 res_l.append(res) print("==============================>") #没有后面的join,或get,则程序整体结束,进程池中的任务还没来得及全部执行完也都跟着主进程一起结束了 pool.close() #关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成 pool.join() #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束 print(res_l) #看到的是<multiprocessing.pool.ApplyResult object at 0x10357c4e0>对象组成的列表,而非最终的结果,但这一步是在join后执行的,证明结果已经计算完毕,剩下的事情就是调用每个对象下的get方法去获取结果 for i in res_l: print(i.get()) #使用get来获取apply_aync的结果,如果是apply,则没有get方法,因为apply是同步执行,立刻获取结果,也根本无需get #二:使用进程池(同步调用,apply) #coding: utf-8 from multiprocessing import Process,Pool import time def func(msg): print( "msg:", msg) time.sleep(0.1) return msg if __name__ == "__main__": pool = Pool(processes = 3) res_l=[] for i in range(10): msg = "hello %d" %(i) res=pool.apply(func, (msg, )) #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去 res_l.append(res) #同步执行,即执行完一个拿到结果,再去执行另外一个 print("==============================>") pool.close() pool.join() #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束 print(res_l) #看到的就是最终的结果组成的列表 for i in res_l: #apply是同步的,所以直接得到结果,没有get()方法 print(i)
# 有几个CPU开几个进程 就是并行 # 合理的范围内开进程 开的多不合理就会降低机器的效率 from multiprocessing import Pool import os ,time,random def work(n):#当成多个任务 print(‘%s is working ‘%os.getpid()) time.sleep(random.randint(1,3 )) return n**2 if __name__==‘__main__‘: p=Pool(4)#进程池 起一个进程执行一个任务 限制为4 #进程池是为了放进程,每接一个进程就自动启一个进程 #从无到有造4个,后面的等着前面的运行完在运转 objs=[] for i in range(10):#10个活 100个1万个都没有问题 # p.apply(work,args=(i,))#申请提交了一个任务 # 同步调用的等 和 阻塞是两种概念 # 串行 第一个任务不结束 下个任务不运行 不是并行 #任务一旦结束就会立刻拿到结果 #要等的时间是work是运行时间 阻塞是睡的那一秒,就是遇到io就进入阻塞态 obj=p.apply_async(work, args=(i,)) # objs.append(obj) # print(obj.get()) #异步调用,没有启动进程和造进程,没运行,没结果 #只负者往进程池里放任务,别的不管 #提交完任务,不会等待,会继续提交下一次任务 # print(obj) p.close() p.join() for obj in objs: print(obj.get()) print(‘==>>end‘) # 异步调用:提交完任务后,不会在原地等待任务结束,会继续提交下一次任务,等到所有任务都结束后,才get结果 # 非阻塞:可能是运行状态,也有可能是就绪状态
练习
#Pool内的进程数默认是cpu核数,假设为4(查看方法os.cpu_count()) #开启6个客户端,会发现2个客户端处于等待状态 #在每个进程内查看pid,会发现pid使用为4个,即多个客户端公用4个进程 from socket import * from multiprocessing import Pool import os server=socket(AF_INET,SOCK_STREAM) server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) server.bind((‘127.0.0.1‘,8080)) server.listen(5) def talk(conn,client_addr): print(‘进程pid: %s‘ %os.getpid()) while True: try: msg=conn.recv(1024) if not msg:break conn.send(msg.upper()) except Exception: break if __name__ == ‘__main__‘: p=Pool() while True: conn,client_addr=server.accept() p.apply_async(talk,args=(conn,client_addr)) # p.apply(talk,args=(conn,client_addr)) #同步的话,则同一时间只有一个客户端能访问
from socket import * client=socket(AF_INET,SOCK_STREAM) client.connect((‘127.0.0.1‘,8080)) while True: msg=input(‘>>: ‘).strip() if not msg:continue client.send(msg.encode(‘utf-8‘)) msg=client.recv(1024) print(msg.decode(‘utf-8‘))
回掉函数
回调函数的场景
进程池中的任何一个任务(子进程)一旦代码执行完毕就会得到执行的结果 然后立刻告诉主进程将(子进程)得到的结果拿来用 那么主进程就会调用一个函数去处理这个结果 而这个函数就叫做回掉函数
我们可以把耗时(阻塞)的任务放到进程池中 然后指定回掉函数(主函数赋值执行)这样主进程在执行回掉函数的时候就可以省去I/O的过程 直接拿到任务的结果
from multiprocessing import Pool import requests import json import os def get_page(url): print(‘<进程%s> get %s‘ %(os.getpid(),url)) respone=requests.get(url) if respone.status_code == 200: return {‘url‘:url,‘text‘:respone.text} def pasrse_page(res): print(‘<进程%s> parse %s‘ %(os.getpid(),res[‘url‘])) parse_res=‘url:<%s> size:[%s]\n‘ %(res[‘url‘],len(res[‘text‘])) with open(‘db.txt‘,‘a‘) as f: f.write(parse_res) if __name__ == ‘__main__‘: urls=[ ‘https://www.baidu.com‘, ‘https://www.python.org‘, ‘https://www.openstack.org‘, ‘https://help.github.com/‘, ‘http://www.sina.com.cn/‘ ] p=Pool(3) res_l=[] for url in urls: res=p.apply_async(get_page,args=(url,),callback=pasrse_page) res_l.append(res) p.close() p.join() print([res.get() for res in res_l]) #拿到的是get_page的结果,其实完全没必要拿该结果,该结果已经传给回调函数处理了 ‘‘‘ 打印结果: <进程3388> get https://www.baidu.com <进程3389> get https://www.python.org <进程3390> get https://www.openstack.org <进程3388> get https://help.github.com/ <进程3387> parse https://www.baidu.com <进程3389> get http://www.sina.com.cn/ <进程3387> parse https://www.python.org <进程3387> parse https://help.github.com/ <进程3387> parse http://www.sina.com.cn/ <进程3387> parse https://www.openstack.org [{‘url‘: ‘https://www.baidu.com‘, ‘text‘: ‘<!DOCTYPE html>\r\n...‘,...}] ‘‘‘
from multiprocessing import Pool import time,random import requests import re def get_page(url,pattern): response=requests.get(url) if response.status_code == 200: return (response.text,pattern) def parse_page(info): page_content,pattern=info res=re.findall(pattern,page_content) for item in res: dic={ ‘index‘:item[0], ‘title‘:item[1], ‘actor‘:item[2].strip()[3:], ‘time‘:item[3][5:], ‘score‘:item[4]+item[5] } print(dic) if __name__ == ‘__main__‘: pattern1=re.compile(r‘<dd>.*?board-index.*?>(\d+)<.*?title="(.*?)".*?star.*?>(.*?)<.*?releasetime.*?>(.*?)<.*?integer.*?>(.*?)<.*?fraction.*?>(.*?)<‘,re.S) url_dic={ ‘http://maoyan.com/board/7‘:pattern1, } p=Pool() res_l=[] for url,pattern in url_dic.items(): res=p.apply_async(get_page,args=(url,pattern),callback=parse_page) res_l.append(res) for i in res_l: i.get() # res=requests.get(‘http://maoyan.com/board/7‘) # print(re.findall(pattern,res.text))
如果在主进程中等待进程池中所有的任务都执行完毕后 再统一处理结果 则无需回掉函数
from multiprocessing import Pool import time,random,os def work(n): time.sleep(1) return n**2 if __name__ == ‘__main__‘: p=Pool() res_l=[] for i in range(10): res=p.apply_async(work,args=(i,)) res_l.append(res) p.close() p.join() #等待进程池中所有进程执行完毕 nums=[] for res in res_l: nums.append(res.get()) #拿到所有结果 print(nums) #主进程拿到所有的处理结果,可以在主进程中进行统一进行处理
以上是关于并发编程的主要内容,如果未能解决你的问题,请参考以下文章