python并发编程之多进程

  • 2020 年 1 月 19 日
  • 筆記

一  multiprocessing模块介绍

 python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。Python提供了multiprocessing。     multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。

  multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。

    需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。

二 Process类的介绍

创建进程的类

Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)    强调:  1. 需要使用关键字的方式来指定参数  2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

 参数介绍:

1 group参数未使用,值始终为None  2  3 target表示调用对象,即子进程要执行的任务  4  5 args表示调用对象的位置参数元组,args=(1,2,'egon',)  6  7 kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}  8  9 name为子进程的名称

 方法介绍:

 1 p.start():启动进程,并调用该子进程中的p.run()   2 p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法   3   4 p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁   5 p.is_alive():如果p仍然运行,返回True   6   7 p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程  

   属性介绍:

1 p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置  2  3 p.name:进程的名称  4  5 p.pid:进程的pid  6  7 p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)  8  9 p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功

三 Process类的使用

注意:在windows中Process()必须放到# if __name__ == '__main__':下

由于Windows没有fork,多处理模块启动一个新的Python进程并导入调用模块。  如果在导入时调用Process(),那么这将启动无限继承的新进程(或直到机器耗尽资源)。  这是隐藏对Process()内部调用的原,使用if __name__ == “__main __”,这个if语句中的语句将不会在导入时被调用。

详细介绍

创建并开启子进程的两种方法

#开进程的方法一:  import time  import random  from multiprocessing import Process  def piao(name):      print('%s piaoing' %name)      time.sleep(random.randrange(1,5))      print('%s piao end' %name)        p1=Process(target=piao,args=('egon',)) #必须加,号  p2=Process(target=piao,args=('alex',))  p3=Process(target=piao,args=('wupeqi',))  p4=Process(target=piao,args=('yuanhao',))    p1.start()  p2.start()  p3.start()  p4.start()  print('主线程')

方式一

#开进程的方法二:  import time  import random  from multiprocessing import Process      class Piao(Process):      def __init__(self,name):          super().__init__()          self.name=name      def run(self):          print('%s piaoing' %self.name)            time.sleep(random.randrange(1,5))          print('%s piao end' %self.name)    p1=Piao('egon')  p2=Piao('alex')  p3=Piao('wupeiqi')  p4=Piao('yuanhao')    p1.start() #start会自动调用run  p2.start()  p3.start()  p4.start()  print('主线程')

方式二

进程直接的内存空间是隔离的

from multiprocessing import Process  n=100 #在windows系统中应该把全局变量定义在if __name__ == '__main__'之上就可以了  def work():      global n      n=0      print('子进程内: ',n)      if __name__ == '__main__':      p=Process(target=work)      p.start()      print('主进程内: ',n)

View Code

Process对象的其他方法或属性

# 进程pid:每一个进程在操作系统内都有一个唯一的id号,称之为pid    # 方法一  '''  from multiprocessing import Process, current_process    import time      def task():      print('%s is running' % current_process().pid)      time.sleep(3)      print('%s is done' % current_process().pid)      if __name__ == '__main__':      p = Process(target=task)      p.start()      print('主进程 ', current_process().pid)  '''    # 方法二  # from multiprocessing import Process  # import os  # import time  #  #  # def task():  #     print('%s is running' % os.getpid())  #     time.sleep(2)  #     print('%s is done' % os.getpid())  #  #  # if __name__ == '__main__':  #     p = Process(target=task)  #     p.start()  #     print('主进程', os.getpid())      from multiprocessing import Process    import os  import time      def task():      print('%s is running 爹是:%s ' % (os.getpid(), os.getppid()))      time.sleep(2)      print('%s is done 爹是:%s ' % (os.getpid(), os.getppid()))    # os.getpid()获得id号;os.getppid())获得父级的id号    if __name__ == '__main__':      p = Process(target=task)      p.start()      p.terminate()   #干死进程      print(p.is_alive())    #判断是否存活      print('主进程:%s  主进程他爹:%s' % (os.getpid(), os.getppid()))

View Code

四 守护进程

主进程创建守护进程

  其一:守护进程会在主进程代码执行结束后就终止

  其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children

注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止

from multiprocessing import Process    import time      def task(name):      print('老太监%s活着.....' % name)      # time.sleep(2)      # print('皇上正在揍老太监%s...'%name)      # time.sleep(2)      # print('老太监%s被揍的嗷嗷叫...' % name)      # time.sleep(2)      # print('老太监%s瘫痪在床上痛苦的呻吟...' % name)      # time.sleep(2)      # print('不久之后老太监%s就呜呼哀哉了' % name)      time.sleep(3)      print('老太监%s正常死亡.....' % name)      if __name__ == '__main__':      p = Process(target=task, args=('张煜',))      # p.daemon = True      p.start()      time.sleep(1)      print('皇上:金毛狗正在死....')      # print('皇上:金毛狗正玩的不亦乐乎')

View Code

五 互斥锁

import json  import time, random  from multiprocessing import Process, Lock      # 查票  def search(name):      with open('db.json', 'rt', encoding='utf_8')as f:          dic = json.load(f)      time.sleep(1)      print('%s 查看到余票为%s' % (name, dic['count']))      # 购票  def get(name):      with open('db.json', 'rt', encoding='utf_8')as f:          dic = json.load(f)      if dic['count'] > 0:          dic['count'] -= 1          time.sleep(random.randint(1, 3))          with open('db.json', 'wt', encoding='utf_8')as f:              json.dump(dic, f)              print('%s 购票成功' % name)      else:          print('%s 查看到没有余票了' % name)      def task(name, mutex):      search(name)  # 并发      mutex.acquire()  # 获取锁      get(name)  # 串行      mutex.release()  # 释放锁        # with mutex:      #     get(name)      if __name__ == '__main__':      mutex = Lock()  # 互斥锁      for i in range(10):          p = Process(target=task, args=('路人甲%s' % i, mutex))          p.start()          # p.join()   # 只能将进程的任务整体变成串行

View Code

 六 队列

进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:多列和管道这两种方式都是使用消息传递的

创建队列的类(底层就是一管道和锁定的方式实现的):

1 Queue([maxsize]):创建共享的进程队列,queue是多进程安全的队列,可以使用queue实现多进程之间的数据传递

参数介绍:

maxsize是队列中允许最大项数,省略则无大小限制

方法介绍:

1 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异常.  3  4 q.get_nowait():同q.get(False)  5 q.put_nowait():同q.put(False)  6  7 q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。  8 q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。  9 q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样

应用

'''  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()) #空了

View Code

生产者消费者模型

在并发编程中使用生产者和消费者模式能够解决大多数并发问题。该模式通过平衡生产线程和消费线程的工作 能力来提升程序的整体处理数据的速度。

为什么要使用生产者和消费者模式

在线程的世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产处理速度很快,而消费者出路速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

什么是生产者消费者模式

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

基于队列实现生产者消费者模型

from multiprocessing import Process,Queue  import time,random,os  def consumer(q):      while True:          res=q.get()          time.sleep(random.randint(1,3))          print('33[45m%s 吃 %s33[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('33[44m%s 生产了 %s33[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()      print('主')

View Code

#生产者消费者模型总结        #程序中有两类角色          一类负责生产数据(生产者)          一类负责处理数据(消费者)        #引入生产者消费者模型为了解决的问题是:          平衡生产者与消费者之间的工作能力,从而提高程序整体处理数据的速度        #如何实现:          生产者<-->队列<——>消费者      #生产者消费者模型实现类程序的解耦和

此时的问题是主进程永远不会结束,原因是:生产者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('33[45m%s 吃 %s33[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('33[44m%s 生产了 %s33[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

注意:结束信号None,不一定要由生产者发,主进程里同样可以发,但主进程需要等生产者结束后才应该发送该信号

其实我们的思路无非是发送结束信号而已,有另外一种队列提供了这种机制

#JoinableQueue([maxsize]):这就像是一个Queue对象,但队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。       #参数介绍:      maxsize是队列中允许最大项数,省略则无大小限制。    #方法介绍:      JoinableQueue的实例p除了与Queue对象相同的方法之外还具有:      q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常      q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止
from multiprocessing import Process, JoinableQueue  import time, random      def producter(name, food, q):      for i in range(3):          res = '%s%s' % (food, i)          time.sleep(random.randint(1, 3))          q.put(res)          print('厨师[%s]生产了<%s>' % (name, res))      def consumer(name, q):      while True:          res = q.get()          time.sleep(random.randint(1, 3))          print('吃货[%s]吃了<%s>' % (name, res))          q.task_done()      if __name__ == '__main__':      q = JoinableQueue()      p1 = Process(target=producter, args=('薛才昌', '泔水', q))      p2 = Process(target=producter, args=('周迟', '屎包子', q))      p3 = Process(target=producter, args=('兰天', '牛鞭', q))      c1 = Process(target=consumer, args=('张煜', q))      c2 = Process(target=consumer, args=('傻狗', q))      c1.daemon = True      c2.daemon = True      p1.start()      p2.start()      p3.start()      c1.start()      c2.start()      p1.join()      p2.join()      p3.join()      q.join() # 主进程等q结束,即q内数据被取干净了        print('主进程')

View Code