• multiprocess模块---进程---进程队列


    首先明白几个概念:

    同步:做完一件事情,再做另外一件事情

    异步:做一件事情的时候,可以再做另外一件事情

    阻塞:recv  sleep accept input recvfrom

    非阻塞:没有遇见上面这些阻塞的情况就是非阻塞

    阻塞与非阻塞这两个概念与程序等待消息通知(无所谓同步或者异步)时的状态有关,

    也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的

     并行:是指两者同时执行,比如多个cpu的情况,微观角度,也就是在一个精确的时间片刻,有不同的程序在执行

    并发:是指资源有限的情况下,两者交替轮流使用资源,宏观角度,在一个时间段上可以看出是同时执行的,比如一个服务器同时处理多个session

    进程三状态:

      (1)就绪(Ready)状态

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

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

      (3)阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,

        例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。

     什么是进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位

     

    multiprocess模块

    从字面意思理解就是:多进程模块,具体来讲是python中一个操作、管理进程的包

    process模块

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

    import os ,time
    from multiprocessing import Process
    def process1():
        print('process1:',os.getppid())
        time.sleep(1)
    
    print(os.getppid())
    if __name__ == '__main__': #这里不写会报错,在mac系统和linux系统不写可以
        # 在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会自动
        # import 启动它的这个文件,而在
        # import 的时候又执行了整个文件。因此如果将process()
        # 直接写在文件中就会无限递归创建子进程报错。所以必须把创建子进程的部分使用if
        # __name__ ==‘__main__’ 判断保护起来,import 的时候 ,就不会递归运行了。
        p=Process(target=process1) #调用函数
        p.start() #启动函数

    给process传参数

    import os
    import time
    from multiprocessing import Process
    def process1(n,name):
        print('process1:',os.getppid())
        print('n:',n,name)
        time.sleep(1)
     
     
    if __name__ == '__main__':
        print(os.getppid())  # 获取当前进程id
        p = Process(target=process1,args=[1,'alex'])  # 调用函数并传参
        p.start()  # 启动进程

     使用说明:

    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为子进程的名称
    View Code

    方法介绍:

     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开启的进程  
    View Code

    属性介绍:

    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字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
    View Code

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

    方法一:以函数的方式

    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('主线程')
    View Code

    方法二:以面向对象的方式

    # import os
    # import time
    # from multiprocessing import Process
    # class MyProcess(Process):
    #     def __init__(self,arg): #只能在这里传参数
    #         super().__init__() #调用父类的init
    #         self.arg=arg
    #     def run(self): #在子进程中之间执行的
    #         print(self.arg)
    #         time.sleep(1)
    #         print('in run',os.getpid())
    #
    # if __name__ == '__main__':
    #     print('main: ',os.getpid())
    #     p=MyProcess(123)
    #     p.start()
    #     print(p.is_alive()) #True
    #     p.terminate() #非阻塞,进程来不及停止
    #     print(p.is_alive()) #True
    #     time.sleep(0.01)
    #     print(p.is_alive()) #False
    View Code

    下面可以使用Process多进程实现socket server的效果:

    服务端:
    import socket
    from multiprocessing import Process
    
    def talk(conn):
        while True:
            conn.send(b'hello')
            print(conn.recv(1024))
    
    if __name__ == '__main__':
        sk=socket.socket()
        sk.bind(('127.0.0.1',8080))
        sk.listen()
        while True:
            conn,addr=sk.accept()
            Process(target=talk,args=(conn,)).start()
    
    客户端:可以启动多个这样的客户端,实现了多并发
    import socket
    
    client=socket.socket()
    client.connect(('127.0.0.1',8080))
    
    while True:
        print(client.recv(1024))
        client.send(b'byebye')
    View Code

    主进程和子进程的关系

    1 主进程等待子进程结束才会结束 ---》 主进程需要回收子进程的资源

    # import os
    # import time
    # from multiprocessing import Process
    # def func():
    #     print('in func before', os.getpid())
    #     time.sleep(10)
    #     print('in func after',os.getpid())
    
    # if __name__ == '__main__':
    #     Process(target=func).start()
    #     print('主进程',os.getpid())
    #     # raise ValueError #主动抛出异常 等待子进程结束主进程才会结束
    #     exit() #同样需要等待子进程结束而结束
    View Code

    2 下面是主进程出异常,立刻终止,并处理的情况,不等待子进程,直接强制退出,最后抛出异常

    # import os
    # import time
    # from multiprocessing import Process
    # def func():
    #     print('in func before', os.getpid())
    #     time.sleep(10)
    #     print('in func after',os.getpid())
    
    # if __name__ == '__main__':
    #     p=Process(target=func)
    #     p.start()
    #     print('主进程',os.getpid())
    #     try:
    #         raise ValueError('111')
    #     except Exception as e :
    #         print(e)
    #         p.terminate() #主进程强制结束,不会等待子进程结束
    #         print('after')
    #         raise #最后抛出异常
    View Code

    3 开启多个子进程,它们之间是异步的,先后顺序和系统的cpu个数有关 ,os.cpu_count() -->查看cpu个数

    # import os
    # import time
    # from multiprocessing import Process
    # n=100
    # def func():
    #     global n
    #     n-=1
    #     print('子进程',os.getpid(),os.getppid())
    
    # if __name__ == '__main__':
    #     print(os.getpid(),os.getppid())
    #     for i in range(9): #自己的电脑是4核超线程,等同于8个cpu,8核以及8核以内,主进程始终在子进程之前执行,8核以后看系统的情况
    #         p=Process(target=func)
    #         p.start()
    #     print('主进程') #主进程和子进程是异步的
    #     print('--->',n) #主进程和子进程是异步的
    
    '''
    15760 14880
    子进程 2676 15760
    主进程
    ---> 100
    子进程 12252 15760
    子进程 11712 15760
    子进程 7012 15760
    子进程 19004 15760
    子进程 14300 15760
    子进程 13360 15760
    子进程 10020 15760
    子进程 20216 15760
    '''
    View Code

    4 使用join的方式,让子进程先于主进程进行 ,这里的先于是先于主进程在子进程启动后面的代码

    # import os
    # import time
    # from multiprocessing import Process
    # n=100
    # def func():
    #     global n
    #     n-=1
    #     print('子进程',os.getpid(),os.getppid())
    
    # if __name__ == '__main__':
    #     print(os.getpid(),os.getppid())
    #     # p.join() #加上join后,子进程就优先于主进程执行
    #     p_lst=[]
    #     for i in range(16):
    #         p = Process(target=func)
    #         p.start()
    #         p_lst.append(p)
    #     for p in p_lst:p.join()
    #     print('主进程')
    #     print('--->',n)
    
    '''
    3464 14880
    子进程 6800 3464
    子进程 16904 3464
    子进程 18740 3464
    子进程 8932 3464
    子进程 15636 3464
    子进程 9148 3464
    子进程 16760 3464
    子进程 5212 3464
    子进程 10036 3464
    子进程 5576 3464
    子进程 10768 3464
    子进程 8020 3464
    子进程 10660 3464
    子进程 8244 3464
    子进程 16700 3464
    子进程 11072 3464
    主进程
    ---> 100 
    '''
    View Code

    说明:以上的几个例子,n的值始终没有变,所以 进程之间的资源,数据都是隔离的

    5  daemon 守护进程,将子进程设置为主进程的守护进程,主进程结束,子进程没结束立即停止

    import os
    import time
    from multiprocessing import Process
    def func():
        print('in func before',os.getpid())
        time.sleep(3)
        print('in func after',os.getpid())
    def func2():
        print('in func2 before',os.getpid())
        time.sleep(5)
        print('in func2 after',os.getpid())
    
    if __name__ == '__main__':
        p=Process(target=func)
        p.daemon=True #设置为一个守护进程,将子进程设置为主进程的守护进程,在start之前设置
        p.start()
        p=Process(target=func2) #func2能正常打印
        p.start()
        time.sleep(1)
        print('主进程')
    #主进程的代码执行完毕之后,守护进程就自动结束了
    
    '''
    in func before 16264
    in func2 before 18400
    主进程
    in func2 after 18400
    '''
    View Code

     6 daemon守护进程的一个迷惑例子:主进程会等待子进程停止而停止,但是不会等待守护进程

    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-------") #这行打印,主进程结束了,foo作为守护子进程也结束了,但是主进程要等待bar子进程
    
    '''
    main-------
    456
    end456
    '''
    View Code

     7 进程的同步锁 Lock,这样一来可以一个一个打印,lock.acquire()  取得锁,lock.release()释放锁

    from multiprocessing import Process,Lock
    import os,time
    def work(lock):
        lock.acquire()
        print('%s is running' % os.getpid())
        time.sleep(2)
        print('%s is done'%os.getpid())
        lock.release()
    
    if __name__ == '__main__':
        lock=Lock()
        for i in range(3):
            p=Process(target=work,args=(lock,))
            p.start()
    
    12472 is running
    12472 is done
    17416 is running
    17416 is done
    20460 is running
    20460 is done
    View Code

    下面是一个模拟抢票例子

    #文件db的内容为:{"count":1}
    #注意一定要用双引号,不然json无法识别
    from multiprocessing import Process,Lock
    import time,json,random
    def search():
        dic=json.load(open('db.txt'))
        print('33[43m剩余票数%s33[0m' %dic['count'])
    
    def get():
        dic=json.load(open('db.txt'))
        time.sleep(0.1) #模拟读数据的网络延迟
        if dic['count'] >0:
            dic['count']-=1
            time.sleep(0.2) #模拟写数据的网络延迟
            json.dump(dic,open('db.txt','w'))
            print('33[43m购票成功33[0m')
    
    def task(lock):
        search()
        lock.acquire()
        get()
        lock.release()
    if __name__ == '__main__':
        lock=Lock()
        for i in range(100): #模拟并发100个客户端抢票
            p=Process(target=task,args=(lock,))
            p.start()
    View Code

    总结:

    #加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
    虽然可以用文件共享数据实现进程间通信,但问题是:
    1.效率低(共享数据基于文件,而文件是硬盘上的数据)
    2.需要自己加锁处理
    
    
    
    #因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。
    1 队列和管道都是将数据存放于内存中
    2 队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,
    我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。
    View Code

    信号量 —— multiprocess.Semaphore(了解)

    互斥锁同时只允许一个线程更改数据,而信号量Semaphore是同时允许一定数量的线程更改数据 。
    假设商场里有4个迷你唱吧,所以同时可以进去4个人,如果来了第五个人就要在外面等待,等到有人出来才能再进去玩。
    实现:
    信号量同步基于内部计数器,每调用一次acquire(),计数器减1;每调用一次release(),计数器加1.当计数器为0时,acquire()调用被阻塞。这是迪科斯彻(Dijkstra)信号量概念P()和V()的Python实现。信号量同步机制适用于访问像服务器这样的有限资源。
    信号量与进程池的概念很像,但是要区分开,信号量涉及到加锁的概念
    介绍Semaphore

    下面看例子

    import time
    import random
    from multiprocessing import Process,Semaphore
    
    def ktv(i,sem):
        sem.acquire()
        print('Person %s 进来唱歌了 '%i)
        time.sleep(random.randint(1,5))
        print('Person %s 从ktv出去了'%i)
        sem.release()
    
    if __name__ == '__main__':
        sem=Semaphore(4)
        for i in range(6):
            Process(target=ktv,args=(i,sem)).start()
    View Code

    结果:

    Person 0 进来唱歌了 
    Person 1 进来唱歌了 
    Person 2 进来唱歌了 
    Person 3 进来唱歌了 
    Person 1 从ktv出去了
    Person 4 进来唱歌了 
    Person 3 从ktv出去了
    Person 5 进来唱歌了 
    Person 0 从ktv出去了
    Person 2 从ktv出去了
    Person 4 从ktv出去了
    Person 5 从ktv出去了
    View Code

     队列

    IPC(Inter-Process Communication)

    进程彼此之间相互隔离,要实现进程间通信(IPC),multiprocessing模块支持两种方式:队列和管道,

    这两种方式都是使用消息传递的

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

    Queue([maxsize]) 维护一个先进先出的秩序,也能进行IPC  
    创建共享的进程队列。
    参数 :maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。
    底层队列使用管道和锁定实现。

    方法介绍:

    vQueue([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()方法)。。
    View Code

    其他方法:了解

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

     简单用法:

    from multiprocessing import Process,Queue
    q = Queue()  #创建共享的进程队列
    q.put(1)  # 将一个值放入队列
    q.put(2)
    q.put('aaa')
    print(q.get())  # 返回q中的一个项目  输出为 1

    下面让双方通信,既能取值,也能增加值,主进程和子进程

    import time
    from multiprocessing import Process,Queue
     
    def wahaha(q):
        print(q.get())  # 取值
        q.put('aaa')  # 增加一个aaa
     
    if __name__ == '__main__':
        q = Queue()  #创建共享的进程队列
        Process(target=wahaha,args=(q,)).start()
        q.put(1)
        time.sleep(0.5)  # 等待0.5秒,让子程序执行完
        print(q.get())  # 取值

    最后看一个复杂点的例子,子进程和子进程通信,---》multiprocessing.freeze_support() 在windows下面执行要加这行,要不然开启一大堆新窗口、进程。

    import os
    import time
    import multiprocessing
    
    # 向queue中输入数据的函数
    def inputQ(queue):
        info = str(os.getpid()) + '(put):' + str(time.asctime())
        queue.put(info)
    
    # 向queue中输出数据的函数
    def outputQ(queue):
        info = queue.get()
        print ('%s%s33[32m%s33[0m'%(str(os.getpid()), '(get):',info))
    
    # Main
    if __name__ == '__main__':
        multiprocessing.freeze_support()
        record1 = []   # store input processes
        record2 = []   # store output processes
        queue = multiprocessing.Queue(3)
    
        # 输入进程
        for i in range(10):
            process = multiprocessing.Process(target=inputQ,args=(queue,))
            process.start()
            record1.append(process)
    
        # 输出进程
        for i in range(10):
            process = multiprocessing.Process(target=outputQ,args=(queue,))
            process.start()
            record2.append(process)
    
        for p in record1:
            p.join()
    
        for p in record2:
            p.join()
    批量放入与批量获取

    结果为:

    7448(get):6612(put):Sun Aug  5 17:59:55 2018
    12268(get):12444(put):Sun Aug  5 17:59:55 2018
    13576(get):9856(put):Sun Aug  5 17:59:55 2018
    19300(get):13636(put):Sun Aug  5 17:59:55 2018
    13068(get):8820(put):Sun Aug  5 17:59:55 2018
    14808(get):14164(put):Sun Aug  5 17:59:55 2018
    1920(get):1452(put):Sun Aug  5 17:59:55 2018
    5064(get):6480(put):Sun Aug  5 17:59:55 2018
    19392(get):12752(put):Sun Aug  5 17:59:55 2018
    10372(get):8968(put):Sun Aug  5 17:59:55 2018
    View Code

     在实际情况中,一般情况都是多并发的,使用q.empty()和q.qsize(),q.full(),q.put,q.get都是不准确的情况,因为其他进程会随时改变队列

  • 相关阅读:
    socket 断线重连
    openwrt lan/wan口自动翻转
    单总线通讯协议
    关于Feign的Fallback处理
    Linux查找占用的端口,并杀死进程
    springCloud--admin监控使用
    解决执行脚本报syntax error: unexpected end of file或syntax error near unexpected token `fi'错误的问题
    Warning: bad syntax, perhaps a bogus '-'? See /usr/share/doc/procps-3.2.8/FAQ
    Shell中[和[[的异同
    Spring Cloud中,如何解决Feign/Ribbon第一次请求失败的问题?
  • 原文地址:https://www.cnblogs.com/mmyy-blog/p/9392272.html
Copyright © 2020-2023  润新知