继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

python并发编程之多进程编程

关注TA
已关注
手记
粉丝
获赞

一、multiprocessing模块介绍

python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。Python提供了multiprocessing。

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

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

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


二、Process类的介绍

1、创建进程的类:Process([group [, target [, name [, args [, kwargs]]]]])

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

强调:

    1.需要使用关键字的方式来指定参数

    2.args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

参数介绍:

    1.group参数未使用,值始终为None

    2.target表示调用对象,即子进程要执行的任务

    3.args表示调用对象的位置参数元组,args=(1,2,'egon',)

    4.kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}

    5.name为子进程的名称

方法介绍:

    1.p.start():启动进程,并调用该子进程中的p.run() 

    2.p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法  

    3.p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁

    4.p.is_alive():如果p仍然运行,返回True

    5.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 p.name:进程的名称

    3.p.pid:进程的pid

    4.p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)

    5.p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)


三、Process类的使用

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

1、创建并开启子进程的两种方式

方式1:用已有的python类

    from multiprocessing import Process

    import time

    def task(name):

            print('%s is running' %name)

            time.sleep(3)

            print('%s is done' %name)

    p=Process(target=task,args=('Alex',))

    p.start()

    print('主函数')

方式2:继承现有的类,自己写一个类

    from multiprocessing import Process

    import time

    class task(Process):

            def __init__(self,name):

                    super().__init__()

                    self.name=name

            def run(self):

                    print('%s is running' %self.name)

                    time.sleep(3)

                    print('%s is done' %self.name)

    p=task('Alex')

    p.start()

    print('主函数')

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

    from multiprocessing import Process

    x=100

    def task():

            global x

            x=0

            print('子进程x值是:',x)

    p=Process(target=task)

    p.start()

    print('主进程x值是:',x)

3、Process对象的join方法

join是主进程等,等待子进程结束

    from multiprocessing import Process

    import time

    def task(name):

            print('%s is running' %name)

            time.sleep(3)

            print('%s is done' %name)

    p=Process(target=task,args=('Alex',))

    p.start()

    p.join()

    print('主函数')

4、Process对象的其他方法或属性(了解)

进程对象的其他方法:terminate,is_alive,pid

    from multiprocessing import Process

    import time

    def task(name):

            print('%s is running' %name)

            time.sleep(3)

            print('%s is done' %name)

    p=Process(target=task,args=('Alex',))

    p.start()

    print(p.pid)

    p.terminate() #关闭进程,不会立即关闭,所以is_alive立刻查看的结果可能还是存活,过一会就就不存活了

    print(p.is_alive())

    print('开始')

    print(p.is_alive())

5、僵尸进程与孤儿进程:http://blog.51cto.com/10630401/2069930


四、守护进程

主进程创建守护进程

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

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

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

    from multiprocessing import Process

    import time

    def task(name):

            print('%s is running' %name)

            time.sleep(3)

            print('%s is done' %name)

    p=Process(target=task,args=('Alex',))

    p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行

    p.start()

    print('主函数')


五、进程同步(锁)

进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,

而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理

1、多个进程共享同一打印终端

代码1:并发运行,效率高,但竞争同一打印终端,带来了打印错乱

    from multiprocessing import Process

    import time,os

    def task():

            print('%s is running' %os.getpid())

            time.sleep(3)

            print('%s is done' %os.getpid())

    for i in range(3):

            p=Process(target=task)

            p.start()

代码2:由并发变成了串行,牺牲了运行效率,但避免了竞争

    from multiprocessing import Process,Lock

    import time,os 

    def task(lock):

            lock.acquire()

            print('%s is running' %os.getpid())

            time.sleep(3)

            print('%s is done' %os.getpid())

            lock.release()

    lock=Lock()

    for i in range(3):

            p=Process(target=task,args=(lock,))

            p.start()

2、模拟购买车票

    略(见练习页)

3、总结

加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。

虽然可以用文件共享数据实现进程间通信,但问题是:

    1)效率低(共享数据基于文件,而文件是硬盘上的数据)

    2)需要自己加锁处理

因此我们最好找寻一种解决方案能够兼顾:

    1)效率高(多个进程共享一块内存的数据)

    2)帮我们处理好锁问题。

这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。

    1)队列和管道都是将数据存放于内存中

    2)队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,

我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。


六、队列

进程彼此之间互相隔离,要实现进程间通信(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)、q.get_nowait():同q.get(False)

    4)、q.put_nowait():同q.put(False)

    5)、q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。

    6)、q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。

    7)、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方法可以禁止这种行为

应用:

    from multiprocessing import Queue

    q=Queue(3)

    q.put('first')

    q.put(2)

    q.put({'count':3})

    # q.put('fourth',block=False) #q.put_nowait('fourth')

    # q.put('fourth',block=True,timeout=3)

    print(q.get())

    print(q.get())

    print(q.get())

    # print(q.get(block=False)) #q.get_nowait()

    # print(q.get(block=True,timeout=3))

七、生产者消费者模型

1、什么是生产者消费者模型

    生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。

    生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯。

    生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

2、为什么要使用生产者和消费者模型

    在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。

    在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。

    同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式

3、生产者与消费者实现

    from multiprocessing import Process,Queue

    import time,random,os

    def consumer(q):

            while True:

                    res=q.get()

                    time.sleep(random.randint(1,3))

                    print('%s 吃 %s' %(os.getpid(),res))

    def producer(q):

            for i in range(10):

                    time.sleep(random.randint(1,3))

                    res='包子%s' %i

                    print(res)

                    q.put(res)

                    print('%s 生产了 %s' %(os.getpid(),res))

    q=Queue()   

    p1=Process(target=producer,args=(q,)) #生产者们:即厨师们    

    c1=Process(target=consumer,args=(q,)) #消费者们:即吃货们

    p1.start()

    c1.start()

    print('主')

4、生产者消费者模型总结

1)程序中有两类角色

    一类负责生产数据(生产者)

    一类负责处理数据(消费者)

2)引入生产者消费者模型为了解决的问题是:

        平衡生产者与消费者之间的工作能力,从而提高程序整体处理数据的速度

3)如何实现:

        生产者<-->队列<——>消费者

4)生产者消费者模型实现类程序的解耦和


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP