• 进程


    并发编程---进程

    一、什么是进程?

    进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

    狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
    广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
    进程的特征:
    动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
    并发性:任何进程都可以同其他进程一起并发执行
    独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
    异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
    结构特征:进程由程序、数据和进程控制块三部分组成。
    多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。

    进程与程序中的区别:

    程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
    而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
    程序可以作为一种软件资料长期存在,而进程是有一定生命期的。
    程序是永久的,进程是暂时的。

    注意:同一个程序执行两次,就会在操作系统中出现两个进程,所以我们可以同时运行一个软件,分别做不同的事情也不会混乱。

    二、进程调度

    1、先来先服务调度算法

    先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。FCFS算法比较有利于长作业(进程),而不利于短作业(进程)。由此可知,本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业(进程)。
    先来先服务调度算法

    2、短作业优先调度算法

    短作业(进程)优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度。但其对长作业不利;不能保证紧迫性作业(进程)被及时处理;作业的长短只是被估算出来的。
    短作业优先调度算法

    3、时间片轮转法

    时间片轮转(Round Robin,RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片,例如,几十毫秒至几百毫秒。如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,等待下一次调度。同时,进程调度程序又去调度当前就绪队列中的第一个进程。
    
    轮转法只能用来调度分配一些可以抢占的资源。这些可以抢占的资源可以随时被剥夺,而且可以将它们再分配给别的进程。CPU是可抢占资源的一种。但打印机等资源是不可抢占的。由于作业调度是对除了CPU之外的所有系统硬件资源的分配,其中包含有不可抢占资源,所以作业调度不使用轮转法。
    在轮转法中,时间片长度的选取非常重要。首先,时间片长度的选择会直接影响到系统的开销和响应时间。如果时间片长度过短,则调度程序抢占处理机的次数增多。这将使进程上下文切换次数也大大增加,从而加重系统开销。反过来,如果时间片长度选择过长,例如,一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕,则轮转法变成了先来先服务法。时间片长度的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的。
          在轮转法中,加入到就绪队列的进程有3种情况:
          一种是分给它的时间片用完,但进程还未完成,回到就绪队列的末尾等待下次调度去继续执行。
          另一种情况是分给该进程的时间片并未用完,只是因为请求I/O或由于进程的互斥与同步关系而被阻塞。当阻塞解除之后再回到就绪队列。
          第三种情况就是新创建进程进入就绪队列。
          如果对这些进程区别对待,给予不同的优先级和时间片从直观上看,可以进一步改善系统服务质量和效率。例如,我们可把就绪队列按照进程到达就绪队列的类型和进程被阻塞时的阻塞原因分成不同的就绪队列,每个队列按FCFS原则排列,各队列之间的进程享有不同的优先级,但同一队列内优先级相同。这样,当一个进程在执行完它的时间片之后,或从睡眠中被唤醒以及被创建之后,将进入不同的就绪队列。
    时间片轮转法

    4、多级反馈队列     

      多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。在采用多级反馈队列调度算法的系统中,调度算法的实施过程如下所述。
    
    (1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。
    
    (2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。
    
    (3) 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。
    多级反馈队列

    三、进程的并行与并发

    并行 : 并行是指两者同时执行,比如赛跑,两个人都在不停的往前跑;(资源够用,比如三个线程,四核的CPU )

    并发 : 并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率。

    区别:

    并行是从微观上,也就是在一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器。
    并发是从宏观上,在一个时间段上可以看出是同时执行的,比如一个服务器同时处理多个session。

    四、同步异步阻塞非阻塞

     1、进程状态

    (1)就绪(Ready)状态

      当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。

    (2)执行/运行(Running)

             状态当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。

    (3)阻塞(Blocked)

           状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。

    2、同步和异步

           所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。

      所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列

    3、同步/异步与阻塞/非阻塞

    (1)同步阻塞形式

      效率最低。拿上面的例子来说,就是你专心排队,什么别的事都不做。

    (2)异步阻塞形式

         异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。

    (3)同步非阻塞形式

        效率低下。

    (4)异步非阻塞形式

       程序没有在两种不同的操作中来回切换

    五、进程的创建与结束

    1、进程的创建

    (1)系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关,运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印)。

    (2)一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)。

    (3)用户的交互式请求,而创建一个新进程(如用户双击暴风影音)。

    (4)一个批处理作业的初始化(只在大型机的批处理系统中应用)。

    注意:无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的。

    2、进程的结束

    (1) 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)。

    (2)出错退出(自愿,python a.py中a.py不存在)。

    (3)严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try...except...)。

    (4)被其他进程杀死(非自愿,如kill -9)。

    六、在python程序中的进程操作:

    1、multiprocess模块

    multiprocess不是一个模块而是python中一个操作、管理进程的包。 之所以叫multi是取自multiple的多功能的意思,在这个包中几乎包含了和进程有关的所有子模块。

     2、multiprocess.process模块

    2.1、process模块介绍

    process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建。

    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字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
    属性介绍
    在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会自动 import 启动它的这个文件,而在 import 的时候又执行了整个文件。因此如果将process()直接写在文件中就会无限递归创建子进程报错。所以必须把创建子进程的部分使用if __name__ ==‘__main__’ 判断保护起来,import 的时候  ,就不会递归运行了。
    在windows中使用process模块的注意事项

    2.2、使用process模块创建进程

    在一个python进程中开启子进程,start方法和并发效果。

    import time
    from multiprocessing import Process
    
    def f(name):
        print('hello', name)
        print('我是子进程')
    
    if __name__ == '__main__':
        p = Process(target=f, args=('bob',))
        p.start()
        time.sleep(1)
        print('执行主进程的内容了')
    在python中启动的第一个子进程
    import time
    from multiprocessing import Process
    
    def f(name):
        print('hello', name)
        time.sleep(1)
        print('我是子进程')
    
    
    if __name__ == '__main__':
        p = Process(target=f, args=('bob',))
        p.start()
        #p.join()
        print('我是父进程')
    join方法
    import os
    from multiprocessing import Process
    
    def f(x):
        print('子进程id :',os.getpid(),'父进程id :',os.getppid())
        return x*x
    
    if __name__ == '__main__':
        print('主进程id :', os.getpid())
        p_lst = []
        for i in range(5):
            p = Process(target=f, args=(i,))
            p.start()
    查看主进程和子进程的进程号

    2.3、开启多个子进程

    import os
    import time
    from multiprocessing import Process
    def func(i):
        time.sleep(3)
        print('%d :子进程%d干的事,父进程%d干的事'%(i,os.getpid(),os.getppid()))
    
    if __name__=='__main__':
        p_lst=[]
        for i in range(10):
            p=Process(target=func,args=(i,))     #实例化,调用线程类,传参数
            p.start()                            #启动线程(类似传达一下消息
            p_lst.append(p) 
        for p in p_lst:
            p.join()                              #阻塞
        print('-------主进程-------')
    实例

    注意:创建一个线程,然后启动(对象名.start),它会在未知的时间里创建线程,所以至于它是不是在主线程之前还是之后启动都不能确定。如果想要主线程在所有线程后等待,那么需要预先创建一个空列表,然后把所有启动的子线程放入列表中,对列表中的子线程总体进行循环阻塞后,启动主线程。

    另一种开启多进程的方法:继承

    import os
    from multiprocessing import Process
    class MyProcess(Process):  #必须创建一个类,必须继承Process方法
        def run(self):               #必须实现run方法
            print('子进程%d'%os.getpid())
            self.walk()
        def walk(self):
            print('子进程walk%d'%os.getpid())
    
    if __name__ =='__main__':
        p=MyProcess()            #先实例化一个对象
        p.start()                   #启动子进程
        p.join()
        print('主进程%d'%os.getpid())  #主进程
    继承开启多进程

    注意:必须创建一个类;必须继承Process类;必须实现run方法。 

    多进程的应用:QQ多人同时聊天

    import socket
    import time
    from  multiprocessing import Process
    def talk(conn):
       conn.send(b"connected")
       ret = conn.recv(1024)
       print(ret)
    if __name__=="__main__":
        sk = socket.socket()
        sk.bind(("127.0.0.1", 8080))
        sk.listen()
        while True:
            conn,addr=sk.accept()
            p=Process(target=talk,args=(conn,))
            p.start()
        conn.close()
        sk.close()
    server
    import socket
    sk=socket.socket()
    sk.connect(("127.0.0.1",8080))
    ret=sk.recv(1024)
    print(ret)
    msg=input(">>>")
    sk.send(msg.encode("utf-8"))
    sk.close()
    client

    2.4、进程的其他方法

    (1)守护进程      p.daemon = True

    守护进程的进程的作用:会随着主进程的代码执行结束而结束,不会等待其他子进程,正常的子进程没有执行完的时候主进程要一直等着。

    注意

    守护进程 要在start之前设置;
    守护进程中 不能再开启子进程;

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

    import time
    from multiprocessing import Process
    def func():
        print('--'*10)
        time.sleep(15)
        print('--'*10)
    
    def cal_time():
        while True:
            time.sleep(1)
            print('过去了1秒')
    
    if __name__ == '__main__':
        p = Process(target=cal_time)
        p.daemon = True     # 一定在开启进程之前设置
        p.start()
        p2 = Process(target=func)  # 15s
        p2.start()
        for i in range(100):    # 10s
            time.sleep(0.1)
            print('*'*i)
        p2.join()
    实例
    (2)进程对象的其他方法:
    is_alive()    是否活着 True代表进程还在 False代表进程不在了
    terminate()   结束一个进程,但是这个进程不会立刻被杀死
    import time
    from multiprocessing import Process
    def func():
        print('wahaha')
        time.sleep(5)
        print('qqxing')
    if __name__ == '__main__':
        p = Process(target=func)
        p.start()
        print(p.is_alive())  #
        time.sleep(0.1)
        p.terminate()        # 关闭进程  异步
        print(p.is_alive())  # ???
        time.sleep(1)
        print(p.is_alive())
    p.is_alive()   # 是否活着 True代表进程还在 False代表进程不在了
    p.terminate()  # 结束一个进程,但是这个进程不会立刻被杀死
    实例
    (3)进程对象的属性
    pid   查看这个进程 进程id
    name  查看这个进程的名字
    import time
    from multiprocessing import Process
    def func():
        print('wahaha')
        time.sleep(5)
        print('qqxing')
    if __name__ == '__main__':
        p = Process(target=func)
        p.start()
        print(p.name,p.pid)
        p.name = '哇哈哈哈'
        print(p.name)
    
    class MyProcess(Process):
        def run(self):
            print('wahaha',self.name,self.pid)
            time.sleep(5)
            print('qqxing',self.name,self.pid)
    if __name__ == '__main__':
        p = MyProcess()
        p.start()
        print(p.pid)
    实例

     3、进程同步

    3.1、锁     multiprocess.Lock

    优点:保证数据安全

    缺点:效率低

    import os
    import time
    import random
    from multiprocessing import Process
    
    def work(n):
        print('%s: %s is running' %(n,os.getpid()))
        time.sleep(random.random())
        print('%s:%s is done' %(n,os.getpid()))
    
    if __name__ == '__main__':
        for i in range(3):
            p=Process(target=work,args=(i,))
            p.start()
    多进程抢占输出资源
    多进程同时抢购余票
    #加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
    虽然可以用文件共享数据实现进程间通信,但问题是:
    1.效率低(共享数据基于文件,而文件是硬盘上的数据)
    2.需要自己加锁处理
    
    #因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。
    队列和管道都是将数据存放于内存中
    队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,
    我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。
    补充

    3.2、信号量    multiprocess.Semaphore

    import time
    import random
    from multiprocessing import Process
    from multiprocessing import Semaphore
    
    def sing(i,sem):
        sem.acquire()
        print("%s:进入 KTV"%i)
        time.sleep(random.randint(1,10))
        print("%s:出 KTV"%i)
        sem.release()
    if __name__=="__main__":
        sem=Semaphore(3)
        for i in range(20):
            p=Process(target=sing,args=(i,sem))
            p.start()
    信号量

    3.3、事件   multiprocess.Event

    python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。
    
        事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
    
    clear:将“Flag”设置为False
    set:将“Flag”设置为True
    事件介绍
    import json
    import time
    import random
    from multiprocessing import Event
    from multiprocessing import Process
    def traffic_light(e):
        while True:
            if e.is_set():
                time.sleep(3)
                print("红灯亮")
                e.clear() #绿变红
            else:
                time.sleep(3)
                print("绿灯亮")
                e.set()   #红变绿
    def car(i,e):
        e.wait()
        print("%s车通过"%i)
    
    if __name__=="__main__":
        e=Event()  #立一个红灯
        tra=Process(target=traffic_light,args=(e,))
        tra.start()   #启动一个进程来控制红绿灯
        for i in range(100):
            if i%6==0:
                time.sleep(random.randint(1,3))
            car_pro=Process(target=car,args=(i,e))
            car_pro.start()
    红绿灯通行

    4、进程间通信    IPC(Inter-Process Communication)

    4.1、队列   multiprocess.Queue

    管道+锁  双向通信 数据安全

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

    Queue([maxsize]) 
    创建共享的进程队列。
    参数 :maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。
    底层队列使用管道和锁定实现。
    Queue([maxsize]) 
    创建共享的进程队列。maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。底层队列使用管道和锁定实现。另外,还需要运行支持线程以便队列中的数据传输到底层管道中。 
    Queue的实例q具有以下方法:
    
    q.get( [ block [ ,timeout ] ] ) 
    返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止。block用于控制阻塞行为,默认为True. 如果设置为False,将引发Queue.Empty异常(定义在Queue模块中)。timeout是可选超时时间,用在阻塞模式中。如果在制定的时间间隔内没有项目变为可用,将引发Queue.Empty异常。
    
    q.get_nowait( ) 
    同q.get(False)方法。
    
    q.put(item [, block [,timeout ] ] ) 
    将item放入队列。如果队列已满,此方法将阻塞至有空间可用为止。block控制阻塞行为,默认为True。如果设置为False,将引发Queue.Empty异常(定义在Queue库模块中)。timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full异常。
    
    q.qsize() 
    返回队列中目前项目的正确数量。此函数的结果并不可靠,因为在返回结果和在稍后程序中使用结果之间,队列中可能添加或删除了项目。在某些系统上,此方法可能引发NotImplementedError异常。
    
    
    q.empty() 
    如果调用此方法时 q为空,返回True。如果其他进程或线程正在往队列中添加项目,结果是不可靠的。也就是说,在返回和使用结果之间,队列中可能已经加入新的项目。
    
    q.full() 
    如果q已满,返回为True. 由于线程的存在,结果也可能是不可靠的(参考q.empty()方法)。。
    队列方法介绍
    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('')
    基于队列实现生产者消费者模型

     下面来看一道例题:

    import time
    import random
    from multiprocessing import Queue
    from multiprocessing import Process
    def producer(q,food):
        for i in range(5):
            q.put('%s-%s'%(food,i))
            print('生产了%s'%food)
            # time.sleep(random.randint(1,3))
            time.sleep(random.random())
        q.put(None)
        q.put(None)
        q.put(None)
    
    def consumer(q,name):
        while True:
            food = q.get()   # 生产者不生产还是生产的慢
            if food == None:break
            print('%s 吃了 %s'%(name,food))
    
    if __name__ == '__main__':
        q = Queue()
        p1 = Process(target=producer,args=(q,'泔水'))
        p1.start()
        p2 = Process(target=producer, args=(q, '骨头'))
        p2.start()
        c1 = Process(target=consumer, args=(q, 'alex'))
        c1.start()
        c2 = Process(target=consumer, args=(q, 'egon'))
        c2.start()
        c3 = Process(target=consumer, args=(q, 'jin'))
        c3.start()
    生产者消费者模型

    关于上面这道例题,我们来研究一下:

    1.消费者要处理多少数据是不确定的
    2.所以只能用while循环来处理数据 ,但是while循环无法结束
    3.需要生产者发送信号
    4.有多少个消费者 就需要发送多少个信号
    5.但是发送的信号数量需要根据 生产者和消费者的数量进行计算,所以非常不方便


    但是JoinableQueue就能够很好的解决这一问题。

    4.2、JoinableQueue([maxsize]) 

    创建可连接的共享进程队列。这就像是一个Queue对象,但队列允许项目的使用者通知生产者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。  

    JoinableQueue的实例p除了与Queue对象相同的方法之外,还具有以下方法:
    
    q.task_done() 
    使用者使用此方法发出信号,表示q.get()返回的项目已经被处理。如果调用此方法的次数大于从队列中删除的项目数量,将引发ValueError异常。
    
    q.join() 
    生产者将使用此方法进行阻塞,直到队列中所有项目均被处理。阻塞将持续到为队列中的每个项目均调用q.task_done()方法为止。 
    下面的例子说明如何建立永远运行的进程,使用和处理队列上的项目。生产者将项目放入队列,并等待它们被处理。
    import time
    import random
    from multiprocessing import Process
    from multiprocessing import JoinableQueue
    # put   q.join
    # get  处理数据 task_done 消费完了
    def producer(q,food):
        for i in range(5):
            q.put('%s-%s'%(food,i))
            print('生产了%s'%food)
            # time.sleep(random.randint(1,3))
            time.sleep(random.random())
        q.join()  # 等待 消费者 把所有的数据都处理完
    
    def consumer(q,name):
        while True:
            food = q.get()   # 生产者不生产还是生产的慢
            print('%s 吃了 %s'%(name,food))
            q.task_done()   #使用者使用此方法发出信号,表示q.get()返回的项目已经被处理
    
    if __name__ == '__main__':
        q = JoinableQueue()
        p1 = Process(target=producer,args=(q,'泔水'))
        p1.start()
        p2 = Process(target=producer, args=(q, '骨头'))
        p2.start()
        c1 = Process(target=consumer, args=(q, 'alex'))
        c1.daemon = True   #守护进程
        c1.start()
        c2 = Process(target=consumer, args=(q, 'egon'))
        c2.daemon = True
        c2.start()
        c3 = Process(target=consumer, args=(q, 'jin'))
        c3.daemon = True
        c3.start()
    
        p1.join()  # 等待p1执行完毕
        p2.join()  # 等待p2执行完毕
    最佳方法

    流程:

    生产者生产的数据全部被消费 —— 生产者进程结束 —— 主进程代码执行结束 —— 消费者守护进程结束

     4.3、管道

    Pipe 管道 双向通信 数据不安全
    #创建管道的类:
    Pipe([duplex]):在进程之间创建一条管道,并返回元组(conn1,conn2),其中conn1,conn2表示管道两端的连接对象,强调一点:必须在产生Process对象之前产生管道
    #参数介绍:
    dumplex:默认管道是全双工的,如果将duplex射成False,conn1只能用于接收,conn2只能用于发送。
    #主要方法:
        conn1.recv():接收conn2.send(obj)发送的对象。如果没有消息可接收,recv方法会一直阻塞。如果连接的另外一端已经关闭,那么recv方法会抛出EOFError。
        conn1.send(obj):通过连接发送对象。obj是与序列化兼容的任意对象
     #其他方法:
    conn1.close():关闭连接。如果conn1被垃圾回收,将自动调用此方法
    conn1.fileno():返回连接使用的整数文件描述符
    conn1.poll([timeout]):如果连接上的数据可用,返回True。timeout指定等待的最长时限。如果省略此参数,方法将立即返回结果。如果将timeout射成None,操作将无限期地等待数据到达。
     
    conn1.recv_bytes([maxlength]):接收c.send_bytes()方法发送的一条完整的字节消息。maxlength指定要接收的最大字节数。如果进入的消息,超过了这个最大值,将引发IOError异常,并且在连接上无法进行进一步读取。如果连接的另外一端已经关闭,再也不存在任何数据,将引发EOFError异常。
    conn.send_bytes(buffer [, offset [, size]]):通过连接发送字节数据缓冲区,buffer是支持缓冲区接口的任意对象,offset是缓冲区中的字节偏移量,而size是要发送字节数。结果数据以单条消息的形式发出,然后调用c.recv_bytes()函数进行接收    
     
    conn1.recv_bytes_into(buffer [, offset]):接收一条完整的字节消息,并把它保存在buffer对象中,该对象支持可写入的缓冲区接口(即bytearray对象或类似的对象)。offset指定缓冲区中放置消息处的字节位移。返回值是收到的字节数。如果消息长度大于可用的缓冲区空间,将引发BufferTooShort异常。
    介绍
    from multiprocessing import Process, Pipe
    
    
    def f(conn):
        conn.send("Hello The_Third_Wave")
        conn.close()
    
    
    if __name__ == '__main__':
        parent_conn, child_conn = Pipe()
        p = Process(target=f, args=(child_conn,))
        p.start()
        print(parent_conn.recv())
        p.join()
    首见pipe

    如果是生产者或消费者中都没有使用管道的某个端点,就应将它关闭。这也说明了为何在生产者中关闭了管道的输出端,在消费者中关闭管道的输入端

    from multiprocessing import Process, Pipe
    
    def f(parent_conn,child_conn):
        #parent_conn.close() #不写close将不会引发EOFError
        while True:
            try:
                print(child_conn.recv())
            except EOFError:
                child_conn.close()
    
    if __name__ == '__main__':
        parent_conn, child_conn = Pipe()
        p = Process(target=f, args=(parent_conn,child_conn,))
        p.start()
        child_conn.close()
        parent_conn.send('hello')
        parent_conn.close()
        p.join()
    引发EOFError
    from multiprocessing import Process,Pipe
    
    def consumer(p,name):
        produce, consume=p
        produce.close()
        while True:
            try:
                baozi=consume.recv()
                print('%s 收到包子:%s' %(name,baozi))
            except EOFError:
                break
    
    def producer(seq,p):
        produce, consume=p
        consume.close()
        for i in seq:
            produce.send(i)
    
    if __name__ == '__main__':
        produce,consume=Pipe()
    
        c1=Process(target=consumer,args=((produce,consume),'c1'))
        c1.start()
    
    
        seq=(i for i in range(10))
        producer(seq,(produce,consume))
    
        produce.close()
        consume.close()
    
        c1.join()
        print('主进程')
    pipe实现生产者消费者模型

    5、进程之间的数据通信

    进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的 虽然进程间数据独立,但可以通过Manager实现数据共享。

    from multiprocessing import Manager,Process,Lock
    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)
    示例

    6、进程池和multiprocess.Pool模块

    Pool([numprocess  [,initializer [, initargs]]]):创建进程池
    1 numprocess:要创建的进程数,如果省略,将默认使用cpu_count()的值
    2 initializer:是每个工作进程启动时要执行的可调用对象,默认为None
    3 initargs:是要传给initializer的参数组
    参数介绍
    1 p.apply(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。
    2 '''需要强调的是:此操作并不会在所有池工作进程中并执行func函数。如果要通过不同参数并发地执行func函数,必须从不同线程调用p.apply()函数或者使用p.apply_async()'''
    3 
    4 p.apply_async(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。
    5 '''此方法的结果是AsyncResult类的实例,callback是可调用对象,接收输入参数。当func的结果变为可用时,将理解传递给callback。callback禁止执行任何阻塞操作,否则将接收其他异步操作中的结果。'''
    6    
    7 p.close():关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
    8 
    9 P.jion():等待所有工作进程退出。此方法只能在close()或teminate()之后调用
    主要方法
    import time
    from multiprocessing import Pool
    # apply
    
    def func(i):
        time.sleep(1)
        i += 1
        # print(i)
        return i+1
    
    if __name__ == '__main__':
        p = Pool(5)
        res_l = []
        for i in range(20):
            # p.apply(func,args=(i,))   # apply是同步提交任务的机制
            res = p.apply_async(func,args=(i,))  # apply_async是异步提交任务的机制
            res_l.append(res)
            # print(res.get())   # 阻塞 :等待着任务结果
        p.close() # close必须加在join之前,不允许再添加新的任务了
        p.join()  # 等待子进程结束再往下执行
        [print(i.get()) for i in res_l]
    示例
  • 相关阅读:
    VUE常用传值方式、父传子、子传父、非父子组件传值
    ios10中禁止用户缩放页面
    TCP MSS
    C++11 之 override
    unordered_set
    c++Lambda
    QUIC实现代码分析
    C++11新特性之十:enable_shared_from_this
    c++11 atomic
    How to Write a QUIC Endpoint Program
  • 原文地址:https://www.cnblogs.com/gaoya666/p/8401169.html
Copyright © 2020-2023  润新知