• python并发编程之多进程


    一.multiprocessing模块 

      multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。

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

      注意:与线程不同,进程没有任何共享的状态,进程修改的数据,仅限于进程内。

    二.Process类

      由该类实例化得到的对象,表示一个子进程中的任务(尚未启动) 

        Process([group [, target [, name [, args [, kwargs]]]]])

           参数介绍: 

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

      方法介绍:

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

      属性介绍:

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

      python类的使用

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

      开启进程的两种方法:

    #方法一
    import time
    import random
    from multiprocessing import Process
    def eat(name):
        print('%s eating' %name)
        time.sleep(random.randrange(1,2))
        print('%s eat end' %name)
    
    
    p1=Process(target=eat,args=('egon',)) #必须加,号
    p2=Process(target=eat,args=('alex',))
    p3=Process(target=eat,args=('wupeqi',))
    p4=Process(target=eat,args=('yuanhao',))
    
    p1.start()
    p2.start()
    p3.start()
    p4.start()
    print('主线程')
    #方法二
    import time
    import random
    from multiprocessing import Process
    
    class Eat(Process):
        def __init__(self,name):
            super().__init__()
            self.name=name
        def run(self):
            print('%s eating' %self.name)
    
            time.sleep(random.randrange(1,5))
            print('%s eat end' %self.name)
    
    p1=Eat('egon')
    p2=Eat('alex')
    p3=Eat('wupeiqi')
    p4=Eat('yuanhao')
    
    p1.start() #start会自动调用run
    p2.start()
    p3.start()
    p4.start()
    print('主线程')

      Process对象的其他方法和属性

    #进程对象的其他方法一:terminate,is_alive
    from multiprocessing import Process
    import time
    import random
    
    class Eat(Process):
        def __init__(self,name):
            self.name=name
            super().__init__()
    
        def run(self):
            print('%s is eating' %self.name)
            time.sleep(random.randrange(1,5))
            print('%s is eat end' %self.name)
    
    
    p1=Eat('egon1')
    p1.start()
    
    p1.terminate()#关闭进程,不会立即关闭,所以is_alive立刻查看的结果可能还是存活
    print(p1.is_alive()) #结果为True
    
    print('开始')
    print(p1.is_alive()) #结果为False
    #进程对象的其他方法二:p.daemon=True,p.join
    from multiprocessing import Process
    import time
    import random
    
    class Eat(Process):
        def __init__(self,name):
            self.name=name
            super().__init__()
        def run(self):
            print('%s is eating' %self.name)
            time.sleep(random.randrange(1,3))
            print('%s is eat end' %self.name)
    
    
    p=Eat('egon')
    p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程死,p跟着一起死
    p.start()
    p.join(0.0001) #等待p停止,等0.0001秒就不再等了
    print('开始')

      进程对象的其他属性

    #进程对象的其他属性:name,pid
    from multiprocessing import Process
    import time
    import random
    class Eat(Process):
        def __init__(self,name):
            # self.name=name
            # super().__init__() #Process的__init__方法会执行self.name=Piao-1,
            #                    #所以加到这里,会覆盖我们的self.name=name
    
            #为我们开启的进程设置名字的做法
            super().__init__()
            self.name=name
    
        def run(self):
            print('%s is eating' %self.name)
            time.sleep(random.randrange(1,3))
            print('%s is eat end' %self.name)
    
    p=Piao('egon')
    p.start()
    print('开始')
    print(p.pid) #查看pid

    三.进程同步(锁)

      注意:加锁的目的是为了保证多个进程修改同一块数据时,同一时间只能有一个修改,即串行的修改,没错,速度是慢了,牺牲了速度而保证了数据安全。

        Lock()互斥锁

    #文件db的内容为:{"count":1}
    #注意一定要用双引号,不然json无法识别
    from multiprocessing import Process,Lock
    import json
    import time
    import random
    import os
    def search():
        dic=json.load(open('db.txt',))
        print('剩余票数%s' %dic['count'])
    
    def get_ticket():
        dic=json.load(open('db.txt',))
        if dic['count'] > 0:
            dic['count']-=1
            json.dump(dic,open('db.txt','w'))
            print('%s 购票成功' %os.getpid())
    def task(mutex):
        search()
        time.sleep(random.randint(1, 3)) #模拟购票一系列繁琐的过程所花费的时间
        mutex.acquire()
        get_ticket()
        mutex.release()
    if __name__ == '__main__':
        mutex=Lock()
        for i in range(50):
            p=Process(target=task,args=(mutex,))
            p.start()

    四.进程间通信方式(队列)

       1.创建队列的类

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

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

       2.主要方法介绍

    q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
    q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
     
    q.get_nowait():同q.get(False)
    q.put_nowait():同q.put(False)
    
    q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
    q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
    q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样

      3.其他方法介绍

    q.cancel_join_thread():不会在进程退出时自动连接后台线程。可以防止join_thread()方法阻塞
    q.close():关闭队列,防止队列中加入更多数据。调用此方法,后台线程将继续写入那些已经入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将调用此方法。关闭队列不会在队列使用者中产生任何类型的数据结束信号或异常。例如,如果某个使用者正在被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。
    q.join_thread():连接队列的后台线程。此方法用于在调用q.close()方法之后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread方法可以禁止这种行为

      4.应用

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

      5.生产者和消费者模型

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

    from multiprocessing import Process,Queue
    import time,random,os
    
    def consumer(q):
        while True:
            time.sleep(random.randint(1,3))
            res=q.get()
            print('33[45m消费者拿到了:%s33[0m' %res)
    
    def producer(seq,q):
        for item in seq:
            time.sleep(random.randint(1,3))
            print('33[46m生产者生产了:%s33[0m' %item)
    
            q.put(item)
    
    if __name__ == '__main__':
        q=Queue()
    
        seq=('包子%s' %i for i in range(10))
        c=Process(target=consumer,args=(q,))
        c.start()
        producer(seq,q)
    
        print('主线程')
    from multiprocessing import Process,Queue
    import time,random,os
    
    
    def consumer(q):
        while True:
            time.sleep(random.randint(1,3))
            res=q.get()
            if res is None:break
            print('33[45m消费者拿到了:%s33[0m' %res)
    
    def producer(seq,q):
        for item in seq:
            time.sleep(random.randint(1,3))
            print('33[46m生产者生产了:%s33[0m' %item)
    
            q.put(item)
    
    if __name__ == '__main__':
        q=Queue()
    
        c=Process(target=consumer,args=(q,))
        c.start()
    
        producer(('包子%s' %i for i in range(10)),q)
        q.put(None)
        c.join()
        print('主线程')
    
    主线程等待消费者结束(生产者发送结束信号给消费者)

      6.创建队列的另外一个类

       JoinableQueue([maxsize]):这就像是一个Queue对象,但队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。

      方法介绍:

      oinableQueue的实例p除了与Queue对象相同的方法之外还具有:

         q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常
           q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止
    from multiprocessing import Process,JoinableQueue
    import time,random
    def consumer(q):
        while True:
            # time.sleep(random.randint(1,2))
            res=q.get()
            print('消费者拿到了 %s' %res)
            q.task_done()
    
    
    def producer(seq,q):
        for item in seq:
            # time.sleep(random.randrange(1,2))
            q.put(item)
            print('生产者做好了 %s' %item)
        q.join()
    
    if __name__ == '__main__':
        q=JoinableQueue()
        seq=('包子%s' %i for i in range(10))
    
        p=Process(target=consumer,args=(q,))
        p.daemon=True #设置为守护进程,在主线程停止时p也停止,但是不用担心,producer内调用q.join保证了consumer已经处理完队列中的所有元素
        p.start()
    
        producer(seq,q)
    
        print('主线程')
    from multiprocessing import Process,JoinableQueue
    import time,random
    def consumer(name,q):
        while True:
            time.sleep(random.randint(1,2))
            res=q.get()
            print('33[45m%s拿到了 %s33[0m' %(name,res))
            q.task_done()
    
    
    def producer(seq,q):
        for item in seq:
            time.sleep(random.randrange(1,2))
            q.put(item)
            print('33[46m生产者做好了 %s33[0m' %item)
        q.join()
    
    if __name__ == '__main__':
        q=JoinableQueue()
        seq=('包子%s' %i for i in range(10))
    
        p1=Process(target=consumer,args=('消费者1',q,))
        p2=Process(target=consumer,args=('消费者2',q,))
        p3=Process(target=consumer,args=('消费者3',q,))
        p1.daemon=True
        p2.daemon=True
        p3.daemon=True
        p1.start()
        p2.start()
        p3.start()
    
        producer(seq,q)
    
        print('主线程')
    
      #一个生产者+多个消费者
    from multiprocessing import Process,JoinableQueue
    import time,random
    def consumer(name,q):
        while True:
            # time.sleep(random.randint(1,2))
            res=q.get()
            print('33[45m%s拿到了 %s33[0m' %(name,res))
            q.task_done()
    
    
    def producer(seq,q):
        for item in seq:
            # time.sleep(random.randrange(1,2))
            q.put(item)
            print('33[46m生产者做好了 %s33[0m' %item)
        q.join()
    
    if __name__ == '__main__':
        q=JoinableQueue()
        seq=['包子%s' %i for i in range(10)] #在windows下无法传入生成器,我们可以用列表解析测试
    
        p1=Process(target=consumer,args=('消费者1',q,))
        p2=Process(target=consumer,args=('消费者2',q,))
        p3=Process(target=consumer,args=('消费者3',q,))
        p1.daemon=True
        p2.daemon=True
        p3.daemon=True
        p1.start()
        p2.start()
        p3.start()
    
        # producer(seq,q) #也可以是下面三行的形式,开启一个新的子进程当生产者,不用主线程当生产者
        p4=Process(target=producer,args=(seq,q))
        p4.start()
        p4.join()
        print('主线程')
    
       #也可以开启一个新的子进程当生产者,不用主线程当生产者
  • 相关阅读:
    子程序的设计
    多重循环程序设计
    汇编语言的分支程序设计与循环程序设计
    代码调试之串口调试2
    毕昇杯模块之光照强度传感器
    毕昇杯之温湿度采集模块
    【CSS】盒子模型 之 IE 与W3C的盒子模型对比
    【css】盒子模型 之 概述
    【css】盒子模型 之 弹性盒模型
    【网络】dns_probe_finished_nxdomain 错误
  • 原文地址:https://www.cnblogs.com/sxh-myblogs/p/7435513.html
Copyright © 2020-2023  润新知