• 第七章|7.1并发编程|多进程


     1、操作系统的介绍

    操作系统帮你封装好硬件复杂的接口,提供比较好的接口给应用程序去调,应用程序调硬件只需调操作系统的接口就可以了;操作系统负责管理运行的多个进程

    多道技术:(针对单核实现并发(看起来是同时运行的)第三代计算机)

    CPU、内存、硬盘

    一 操作系统的作用:
        1:隐藏丑陋复杂的硬件接口,提供良好的抽象接口
        2:管理、调度进程,并且将多个进程对硬件的竞争变得有序
    
    二 多道技术:(时间多路复用和空间多路复用+硬件上支持隔离)
        1.产生背景:针对单核,实现并发
        ps:
        现在的主机一般是多核,那么每个核都会利用多道技术
        有4个cpu,运行于cpu1的某个程序遇到io阻塞,会等到io结束再重新调度,会被调度到4个
        cpu中的任意一个,具体由操作系统调度算法决定。
    
        2.空间上的复用:如内存中同时有多道程序;  (实现物理层面的隔离,都读到内存空间里)
        3.时间上的复用:复用一个cpu的时间片; 遇到I/O操作去硬盘读取内容时cpu要去切(提升效率)、遇到一个程序运行时间过长了也要切(实现并发的效果反而会降低效率);
           强调:遇到io切,占用cpu时间过长也切,核心在于切之前将进程的状态保存下来,这样
                才能保证下次切换回来时,能基于上次切走的位置继续运行

    多核,叫并行了,真正意义上的并行;

    2、并发编程之多进程

    2.1进程理论

    进程:正在进行的一个过程或者说一个任务。而负责执行任务则是cpu。

    程序仅仅只是一堆代码而已,而进程指的是程序的运行过程。同一个程序执行两次,那也是两个进程。

    无论是并行还是并发,在用户看来都是'同时'运行的,不管是进程还是线程,都只是一个任务而已,真是干活的是cpu,cpu来做这些任务,而一个cpu同一时刻只能执行一个任务。

    并发和并行

    并发:是伪并行,即看起来是同时运行。单个cpu+多道技术就可以实现并发。

    并行:同时运行,只有具备多个cpu才能实现并行

    单核下,可以利用多道技术,多个核,每个核也都可以利用多道技术(多道技术是针对单核而言的

    有四个核,六个任务,这样同一时间有四个任务被执行,假设分别被分配给了cpu1,cpu2,cpu3,cpu4,

    一旦任务1遇到I/O就被迫中断执行,此时任务5就拿到cpu1的时间片去执行,这就是单核下的多道技术

    而一旦任务1的I/O结束了,操作系统会重新调用它(需知进程的调度、分配给哪个cpu运行,由操作系统说了算)

    可能被分配给四个cpu中的任意一个去执行

    进程的层次结构

    无论UNIX还是windows,进程只有一个父进程,不同的是:

    1. 在UNIX中所有的进程,都是以init进程为根,组成树形结构。父子进程共同组成一个进程组,这样,当从键盘发出一个信号时,该信号被送给当前与键盘相关的进程组中的所有成员。

    2. 在windows中,没有进程层次的概念,所有的进程都是地位相同的,唯一类似于进程层次的暗示,是在创建进程时,父进程得到一个特别的令牌(称为句柄),该句柄可以用来控制子进程,但是父进程有权把该句柄传给其他子进程,这样就没有层次了。

    2.2开启进程的两种方式:

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

      Python提供了multiprocessing,multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,>提供了Process、Queue、Pipe、Lock等组件。

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

     进程:正在进行的一个过程或者说一个任务。而负责执行任务则是cpu

    Process类的介绍

    创建进程的类

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

    参数介绍

    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.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
    
    p.name:进程的名称

    Process类的使用

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

    ##创建子进程方式一
    from
    multiprocessing import Process import time def task(name): print('%s is done'%name) time.sleep(3) print('%s is done'%name) if __name__ == '__main__': p = Process(target=task,args=('子进程1',)) #实例化得到一个对象 #Process(target=task,kwargs={'name':'子进程1'}) p.start()#仅仅是给操作系统发送个信号 (我父进程有个子进程你去给我开吧);父进程不会等着它开启 #子进程的初始状态跟父进程一样,但运行是独立的 print('') #打印 主 子进程1 is done 子进程1 is done
    ###方式二
    from multiprocessing import Process
    import time
    class MyProcess(Process):
        def __init__(self,name):
            super().__init__()
            self.name = name
        def run(self): #固定写法,子进程
            print('%s is done'%self.name)
            time.sleep(3)
            print('%s is done'%self.name)
    if __name__ == '__main__':
        p = MyProcess('子进程')
        p.start() #调用那个run;start会自动调用run方法。
        print('')
    #打印:

      主
      子进程 is done
      子进程 is done

    查看pid 

    from multiprocessing import Process
    import time, os
    def task():
        print('%s is done,parent id is %s'%(os.getpid(),os.getppid())) #看进程编号,看那个在执行
        time.sleep(3)
        print('%s is done,parent id is %s'%(os.getpid(),os.getppid()))#os.getppid()就是它父的编号
    if __name__ == '__main__':
        p = Process(target=task, ) #
        p.start()
        print('',os.getpid(),os.getppid())#os.getppid()是pycharm的,主进程的爹
    
    #打印:
    主 6816 3580
    6704 is done,parent id is 6816
    6704 is done,parent id is 6816

    僵尸进程和孤儿进程(了解)

    父进程开启一个子进程,他自己结束之后,会等子进程运行完,他才会结束。

    所有的子进程都会有个僵尸进程,他死了之后,会保留尸体,父进程可以随时查看他的状态。子进程在父进程一直不死的情况下会有害,多个僵尸进程占用内存。

    父进程先死掉,子进程还在就是孤儿进程;在linux系统之上会有一个INIT,这个进程是所有进程它爹,INIT相当于孤儿院,由它来接管;

    2.3Process对象的其他属性和方法

    join()

    from multiprocessing import Process
    import time, os
    def task():
        print('%s is done,parent id is %s'%(os.getpid(),os.getppid())) #看进程编号,看哪个在执行
        time.sleep(3)
        print('%s is done,parent id is %s'%(os.getpid(),os.getppid()))#os.getppid()就是它父的编号
    if __name__ == '__main__':
        p = Process(target=task, ) #
        p.start()
        p.join()#主进程在等p结束,这一行不过去永远不会执行下一步,最后执行主进程;卡住的是主进程而绝非子进程p 
        print('',os.getpid(),os.getppid())
        print(p.pid) #进程结束掉之后,子进程运行完之后可以查看它的pid,但是主进程结束后就没有了,僵尸儿子才会被回收掉。在cmd命令下查看5300是否还存在,结果是被回收了
        
    #打印:

    5300 is done,parent id is 4780
    5300 is done,parent id is 4780
    主 4780 3580
    5300

    from multiprocessing import Process
    import time, os
    def task(name):
        print('%s is dones'%name) 
        time.sleep(3)
    
    if __name__ == '__main__':
        p1 = Process(target=task, args=('子进程1',)) #
        p2 = Process(target=task, args=('子进程2',))
        p3 = Process(target=task, args=('子进程3',))
        p1.start()
        p2.start()
        p3.start()
        print('',os.getpid(),os.getppid())
    
    #打印 结果不唯一,p1.start()、p2、p3只是给给操作系统发个信息,操作系统先运行谁说不准,多打印几次结果就不一样了
    主 4804 3580
    子进程2 is dones
    子进程1 is dones
    子进程3 is dones
    from multiprocessing import Process
    import time, os
    def task(name, n):
        print('%s is dones'%name) #看进程编号,看那个在执行
        time.sleep(n)
    
    if __name__ == '__main__':
        start = time.time()
        p1 = Process(target=task, args=('子进程1',5)) #
        p2 = Process(target=task, args=('子进程2',3))
        p3 = Process(target=task, args=('子进程3',2))
        p_l = [p1,p2,p3]
        # p1.start()
        # p2.start()
        # p3.start() #也可以改成for循环
        for p in p_l:
            p.start()  #进程只要start就会开始运行了,p1、p2、p3.start()时,系统已经有3个并发的进程了。
    
        # p1.join() #join是让主进程等,而p1-p3仍然是并发执行的,p1.join()的时候,其余p2、p3仍然在运行,等p1.join()结束,可能p2、p3早已经结束,这样p2.join()、p3.join()直接通过检测无需等待。
        # p2.join()
        # p3.join()
        for p in p_l:
            p.join()
    
        print('',(time.time()-start)) #等的就是那个最长的时间,程序仍然是并发执行,不是串连执行
    
    打印:
    子进程1 is dones
    子进程3 is dones
    子进程2 is dones
    主 5.244300127029419
    from multiprocessing import Process
    import time, os
    def task(name, n):
        print('%s is dones'%name) #看进程编号,看那个在执行
        time.sleep(n)
    
    if __name__ == '__main__':
        start = time.time()
        p1 = Process(target=task, args=('子进程1',5)) #
        p2 = Process(target=task, args=('子进程2',3))
        p3 = Process(target=task, args=('子进程3',2))
        p1.start()
        p1.join()
        p2.start()
        p2.join()
        p3.start()
        p3.join()
        print('',(time.time()-start)) #这样就是串行了,等1结束,然后等2、等3
    
    #打印
    子进程1 is dones
    子进程2 is dones
    子进程3 is dones
    主 10.41359567642212

    is_alive()、terminate()、name属性

    ### is_alive()判断是否存活
    from multiprocessing import Process
    import time, os
    def task():
        print('%s is done,parent id is %s'%(os.getpid(),os.getppid()))
        time.sleep(3)
        print('%s is done,parent id is %s'%(os.getpid(),os.getppid()))
    if __name__ == '__main__':
        p = Process(target=task, ) #
        p.start()
        print(p.is_alive())
        p.join()
        print('',os.getpid(),os.getppid())
        print(p.pid)
        print(p.is_alive()) #看看p是否还活着
    #打印:

      True
      5344 is done,parent id is 3068
      5344 is done,parent id is 3068
      主 3068 3708
      5344
      False

    ###terminate() 、  name属性
    from
    multiprocessing import Process import time, os def task(): print('%s is done,parent id is %s'%(os.getpid(),os.getppid())) time.sleep(3) print('%s is done,parent id is %s'%(os.getpid(),os.getppid())) if __name__ == '__main__': p = Process(target=task, name= 'sub-process') #可以用关键字参数来指定进程名字 p.start() p.terminate()#杀死这个p进程,它只是给操作系统发个信息,杀死进程是要回收内存空间的是由操作系统说了算 time.sleep(3)#不加这个杀不死,要等操作系统缓存下 print(p.is_alive()) print('') print(p.name) #不指定就默认Process-1 #打印: False 主 sub-process

    练习题

    1、进程之间的内存空间是共享的还是隔离的?下述代码的执行结果是什么?

    进程内存空间是隔离的。

    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()
        p.join()##确保子进程已经运行过了
        print('主进程内: ',n)
    
    打印:
    子进程内:  0
    主进程内:  100

    2、基于多进程实现并发的套接字通信?

    ##服务端
    from socket import *
    from multiprocessing import Process
    def talk(conn):
        while True:
            try:
                data = conn.recv(1024) #干通信的活 
                if not data:break
                conn.send(data.upper())
            except ConnectionRefusedError:
                break
        conn.close()
    def server(ip, port):
        server = socket(AF_INET, SOCK_STREAM)
        server.bind((ip, port))
        server.listen(5)
        while True:
            conn,addr = server.accept() ##主进程专门干链接的活,一直建链接,每建成一个链接就起一个进程跟客户端通信
            p = Process(target=talk,args=(conn,))
            p.start()
        server.close()
    if __name__ == '__main__':
        server('127.0.0.1',8080)
    ##客户端
    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'))
        data = client.recv(1024)
        print(data.decode('utf-8'))

    3、改写下列程序,分别别实现下述打印效果

    # from multiprocessing import Process
    # import time
    # import random
    #
    # def task(n):
    #     time.sleep(random.randint(1,3))
    #     print('-------->%s' %n)
    #
    # if __name__ == '__main__':
    #     p1=Process(target=task,args=(1,))
    #     p2=Process(target=task,args=(2,))
    #     p3=Process(target=task,args=(3,))
    #
    #     p1.start()
    #     p2.start()
    #     p3.start()
    #
    #     p1.join()
    #     p2.join()
    #     p3.join()
    #     print('-------->4')

    打印:#---->4最后打印

    -------->1
    -------->3
    -------->2
    -------->4

    from multiprocessing import Process
    import time
    import random
    
    def task(n):
        time.sleep(random.randint(1,3))
        print('-------->%s' %n)
    
    if __name__ == '__main__':
        p1=Process(target=task,args=(1,))
        p2=Process(target=task,args=(2,))
        p3=Process(target=task,args=(3,))
    
        p1.start()
        p1.join()
        p2.start()
        p2.join()
        p3.start()
        p3.join()
    
        print('-------->4')

    打印:#按顺序输出

    -------->1
    -------->2
    -------->3
    -------->4

    2.4守护进程

    主进程创建子进程,然后将该进程设置成守护自己的进程,守护进程就好比崇祯皇帝身边的老太监,崇祯皇帝已死老太监就跟着殉葬了。

    守护进程需要强调两点:

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

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

      如果我们有两个任务需要并发执行,那么开一个主进程和一个子进程分别去执行就ok了,如果子进程的任务在主进程任务结束后就没有存在的必要了,那么该子进程应该在开启前就被设置成守护进程。主进程代码运行结束,守护进程随即终止。

    from multiprocessing import Process
    import time
    import random
    def task(name):
        print('%s is piaoing' %name)
        time.sleep(random.randrange(1,3))
        print('%s is piao end' %name)
    
    if __name__ == '__main__':
        p=Process(target=task,args=('egon',))
        p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行
        p.start()
        print('') #只要终端打印出这一行内容,那么守护进程p也就跟着结束掉了
    #打印:
    主 #把p.daemon=True注释掉,就可以打印出egon is piaoing egon is piao end
    from multiprocessing import Process
    import time
    def task(name):
        print('%s is done'%name)
        time.sleep(3)
        p = Process(target=time.sleep, args=(3,)) #再开子进程会报错,守护进程不让开
        p.start()
    
    if __name__ == '__main__':
        p = Process(target=task, args=('子进程1',))
        p.daemon = True #要在开始之前设置
        p.start()
    
        p.join() #确保了守护进程确实运行完毕了
        print('')  #只要终端打印出这一行内容,那么守护进程p也就跟着结束掉了。
    
    打印:
    子进程1 is done
    Process Process-1:
    Traceback (most recent call last):
      File 
    .....
    AssertionError: daemonic processes are not allowed to have children
    主
    #练习
    from multiprocessing import Process
    import time
    def foo(): #守护进程
        print(123)
        time.sleep(1)
        print("end123")
    
    def bar():
        print(456)
        time.sleep(3)
        print("end456")
    if __name__ == '__main__':
        p1=Process(target=foo)
        p2=Process(target=bar)
    
        p1.daemon=True
        p1.start()
        p2.start()
        print("main-------") #运行到这一行的时候p1 p2还没出来;
    
    打印:
    main------- #只要它出现,p1立马就死了;
    456
    end456

    2.5互斥锁

    进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,而共享带来的是竞争,竞争带来的结果就是错乱。

    如何控制,就是加锁处理。而互斥锁的意思就是互相排斥,如果把多个进程比喻为多个人,互斥锁的工作原理就是多个人都要去争抢同一个资源:卫生间,一个人抢到卫生间后上一把锁,其他人都要等着,等到这个完成任务后释放锁,其他人才有可能有一个抢到......所以互斥锁的原理,就是把并发改成穿行,降低了效率,但保证了数据安全不错乱。

    from multiprocessing import Process
    import time
    
    def task(name):
        print('%s 1' %name)
        time.sleep(1)
        print('%s 2' %name)
        time.sleep(1)
        print('%s 3' %name)
    
    if __name__ == '__main__':
        for i in range(3):
            p=Process(target=task,args=('进程%s' %i,))
            p.start()
    
    打印:#竞争 谁抢这终端了谁打印
    进程0 1
    进程1 1
    进程2 1
    进程0 2
    进程1 2
    进程2 2
    进程0 3
    进程1 3
    进程2 3
    from multiprocessing import Process, Lock
    import time
    def task(name, mutex):  #所有的子进程来抢这把锁,然后来运行下面这段代码;运行完之后,其他字进程才进入
        mutex.acquire() #加锁
        print('%s 1' %name)
        time.sleep(1)
        print('%s 2' %name)
        time.sleep(1)
        print('%s 3' %name)
        mutex.release() #运行完之后要释放
    if __name__ == '__main__':
        mutex = Lock() #父进程里边早出来这个变量对象,子进程也会拷贝一份
        for i in range(3):
            p=Process(target=task,args=('进程%s' %i, mutex))#传给子进程,所有的子进程用一把锁
            p.start()
    
    #打印:
    进程0 1
    进程0 2
    进程0 3
    进程2 1
    进程2 2
    进程2 3
    进程1 1
    进程1 2
    进程1 3

    模拟抢票

    #查看票数的时候是并发购票是串,一个一个来,加个互斥锁

      #文件db.txt的内容为:{"count":1} #一定要用双引号,不然json无法识别

    from multiprocessing import Process,Lock
    import json
    import time
    
    def search(name):
        time.sleep(1)
        dic=json.load(open('db.txt','r',encoding='utf-8'))
        print('<%s> 查看到剩余票数【%s】' %(name,dic['count']))
    def get(name):
        time.sleep(1)
        dic=json.load(open('db.txt','r',encoding='utf-8'))
        if dic['count'] > 0:
            dic['count']-=1
            time.sleep(3) ##模拟写数据的网络延迟
            json.dump(dic,open('db.txt','w',encoding='utf-8'))
            print('<%s> 购票成功' %name)
    def task(name,mutex):
        search(name)
        mutex.acquire()
        get(name)
        mutex.release()
    
    if __name__ == '__main__':
        mutex=Lock()
        for i in range(10): #模拟并发10个客户端抢票
            p=Process(target=task,args=('路人%s' %i,mutex))
            p.start()
    
    打印:
    <路人0> 查看到剩余票数【1<路人2> 查看到剩余票数【1<路人1> 查看到剩余票数【1<路人4> 查看到剩余票数【1<路人3> 查看到剩余票数【1<路人6> 查看到剩余票数【1<路人5> 查看到剩余票数【1<路人8> 查看到剩余票数【1<路人7> 查看到剩余票数【1<路人9> 查看到剩余票数【1<路人0> 购票成功

    互斥锁和join的区别

    互斥锁就是把并发变成串行,保证多个进程去修改同一个数据的时候大家一个一个修改,牺牲了效率保证了数据的安全;

    join也可以使程序串行

    发现使用join将并发改成串行,确实能保证数据安全,但问题是连查票操作也变成只能一个一个人去查了,很明显大家查票时应该是并发地去查询而无需考虑数据准确与否,此时join与互斥锁的区别就显而易见了,join是将一个任务整体串行,而互斥锁的好处则是可以将一个任务中的某一段代码串行,比如只让task函数中的get任务串行。

    from multiprocessing import Process,Lock
    import json
    import time
    def search(name):
        time.sleep(1)
        dic=json.load(open('db.txt','r',encoding='utf-8'))
        print('<%s> 查看到剩余票数【%s】' %(name,dic['count']))
    def get(name):
        time.sleep(1)
        dic=json.load(open('db.txt','r',encoding='utf-8'))
        if dic['count'] > 0:
            dic['count']-=1
            time.sleep(3)
            json.dump(dic,open('db.txt','w',encoding='utf-8'))
            print('<%s> 购票成功' %name)
        else:
            print('<%s> 购票失败' %name)
    def task(name,):
        search(name)
        # mutex.acquire()
        get(name)
        # mutex.release()
    if __name__ == '__main__':
        # mutex=Lock()
        for i in range(10):
            p=Process(target=task,args=('路人%s' %i,))
            p.start()
            p.join()#把进程整个代码都串行了,看票也是串行的
    
    打印:
    <路人0> 查看到剩余票数【0<路人0> 购票失败
    <路人1> 查看到剩余票数【0<路人1> 购票失败
    <路人2> 查看到剩余票数【0<路人2> 购票失败
    <路人3> 查看到剩余票数【0<路人3> 购票失败
    <路人4> 查看到剩余票数【0<路人4> 购票失败
    <路人5> 查看到剩余票数【0<路人5> 购票失败
    <路人6> 查看到剩余票数【0<路人6> 购票失败
    <路人7> 查看到剩余票数【0<路人7> 购票失败
    <路人8> 查看到剩余票数【0<路人8> 购票失败
    <路人9> 查看到剩余票数【0<路人9> 购票失败

    总结

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

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

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

    2、需要自己加锁处理

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

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

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

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

    队列和管道都是将数据存放于内存中,而队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,因而队列才是进程间通信的最佳选择。

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

    2.6队列的使用

    进程之间内存之间是互相隔离的,但可以共享(加锁)数据,读写效率低;我用一块共享内存,读写效率就高了,也解决了加锁的问题

    IPC,进程之间的通信:队列和管道,用的都是内存的空间,共享内存;队列就是管道加锁实现的

    队列,先进先出

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

    Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
    maxsize是队列中允许最大项数,省略则无大小限制。
    但需要明确:
        1、队列内存放的是消息而非大数据
        2、队列占用的是内存空间,因而maxsize即便是无大小限制也受限于内存大小

    主要方法:

    q.put方法用以插入数据到队列中。
    q.get方法可以从队列读取并且删除一个元素。

    使用:

    from multiprocessing import Queue
    q=Queue(3)
    
    q.put('hello')
    q.put({'a':1})
    q.put([3,3,3,])
    print(q.full()) #True
    # q.put(4) #只能放三个数据,然后会把它给锁上,卡着不能运行了,阻塞了
    
    print(q.get()) #hello
    print(q.get()) #{'a':1}
    print(q.get()) #[3,3,3,]
    print(q.empty()) #清空了
    
    print(q.get())
    
    打印: #最后空数据,卡着了,取不到了
    True
    hello
    {'a': 1}
    [3, 3, 3]
    True

    2.7生产者消费者模型

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

    什么是生产者和消费者模式?

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

    这个阻塞队列就是用来给生产者和消费者解耦的。

    from multiprocessing import Process,Queue
    import time
    
    def producer(q):
        for i in range(6): #模拟生产6个数据
            res='包子%s' %i
            time.sleep(0.5)
            print('生产者生产了%s' %res)
    
            q.put(res)  #生产完不是直接给消费者,丢到队列里边
    
    def consumer(q):
        while True: #不知道接几次
            res=q.get() #接结果
            if res is None:break #消费者最后会收到那个None,这样就不会卡那了
            time.sleep(1)
            print('消费者吃了%s' % 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()
        c2.start()
    
        p1.join() #主进程保证p1这个子进程运行完了
        p2.join()
        p3.join()
        q.put(None) #None代表结束信号,有几个消费者来几个信号;;应该是在主进程里边确保所有的生产者都生产结束之后才发结束信号
        q.put(None) #有几个消费者就应该要有几个信号,
        print('')
    打印:
    生产者生产了包子0
    生产者生产了包子0
    生产者生产了包子0
    生产者生产了包子1
    生产者生产了包子1
    生产者生产了包子1
    消费者吃了包子0
    生产者生产了包子2
    生产者生产了包子2
    消费者吃了包子0
    生产者生产了包子2
    生产者生产了包子3
    生产者生产了包子3
    生产者生产了包子3
    消费者吃了包子0
    生产者生产了包子4
    消费者吃了包子1
    生产者生产了包子4
    生产者生产了包子4
    生产者生产了包子5
    生产者生产了包子5
    生产者生产了包子5
    主
    消费者吃了包子1
    消费者吃了包子1
    消费者吃了包子2
    消费者吃了包子2
    消费者吃了包子2
    消费者吃了包子3
    消费者吃了包子3
    消费者吃了包子3
    消费者吃了包子4
    消费者吃了包子4
    消费者吃了包子4
    消费者吃了包子5
    消费者吃了包子5
    消费者吃了包子5

    用处:程序中有两类角色,一类是生产数据、另一类是处理数据,就可以引入生产者消费者模型来解决耦合的问题; 生产者<--->队列<--->消费者

    好处:平衡生产者与消费者之间的速度差;程序解开耦合。

    用process下每个实现,生产者、消费者、和那个q都要在一个计算机上,集中式的;带来的问题:稳定性(计算机断了,集中式都断掉了)、效率和性能(一台计算机总归是有极限的)。    把程序各个组件分配出去,分给各个机器,效率和稳定性都提升了。基于网络套接字,把消息发来取走;基于网络通信的消息队列典型代表是Rabbitmq。

    JoinableQueue

    在有多个生产者和多个消费者时,有几个消费者就需要发送几次结束信号:相当low,可以用joinableQueue来解决

    from multiprocessing import Process,JoinableQueue
    import time
    
    def producer(q):
        for i in range(2):
            res='包子%s' %i
            time.sleep(0.5)
            print('生产者生产了%s' %res)
    
            q.put(res)
        q.join() #所有的生产者生产完了之后,队列都取没了就算结束了; 生产完了之后就在这等着,数据都取走之后就不等了
    
    def consumer(q):
        while True:
            res=q.get() #卡在这里;东西取没了就算结束
            if res is None:break
            time.sleep(1)
            print('消费者吃了%s' % res)
            q.task_done() #提供的接口,从队列里边取走的信号  消费者给生产者发个信号,告诉生产者有一个数据取走了;跟原来反着的,原来是生产者给消费者发信号
    if __name__ == '__main__':
        #容器
        q=JoinableQueue()
        #q.join()#等队列执行完,队列取没了就算完了
    
        #生产者们
        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  #把c1、c2设置成守护进程,这样就可以正常结束了
        c2.daemon=True
    
        p1.start()
        p2.start()
        p3.start()
        c1.start()
        c2.start()
    
        p1.join() #保证生产者结束完了
        p2.join()
        p3.join() #执行到这一步,可以保证 q被取空了,消费者已经都完全取走了。
        print('') #这一行运行完之后,消费者就没意义存在了,这样就用到了守护进程
    打印
    生产者生产了包子0
    生产者生产了包子0
    生产者生产了包子0
    生产者生产了包子1
    生产者生产了包子1
    生产者生产了包子1
    消费者吃了包子0
    消费者吃了包子0
    消费者吃了包子0
    消费者吃了包子1
    消费者吃了包子1
    消费者吃了包子1
    主
  • 相关阅读:
    130517Dev GridControl建立多行复杂表头(Banded View)时,统计列与对应列无法对齐的解决办法
    C&C++标准库
    Linux操作系统下的多线程编程详细解析
    Ubuntu12.04用户以root身份登录
    ubuntu永久修改主机名
    linux信号 linux signal
    淘宝api 登录验证
    淘宝开店 防骗 易赛加款诈骗|冲q币恶意差评
    面试..
    test
  • 原文地址:https://www.cnblogs.com/shengyang17/p/8877275.html
Copyright © 2020-2023  润新知