• 进程


     

    1 进程相关概念

    1.1 进程

      进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

      在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

    #狭义定义:
        进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
    #广义定义:
        进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

    1.2 同步/异步

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

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

    1.3 阻塞/非阻塞

      阻塞和非阻塞这两个概念与程序等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的。

    1.4 并发/并行

      并行 : 并行是指多个任务同时执行,比如两个男人同时在给自己女朋友发微信。

      并发 : 并发是多个任务交替轮流使用资源,比如一个男人在给他7个女朋友发微信,只要他发的够快,宏观上来说他在同时聊7个人。

    1.5 进程状态与调度

      在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪,运行和阻塞。

    #(1)就绪(Ready)状态:当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。
    
    #(2)执行/运行(Running)状态:当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。
    
    #(3)阻塞(Blocked)状态:正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。

    2 Python中使用多进程

      运行中的程序就是一个进程。所有的进程都是通过它的父进程来创建的。因此,运行起来的python程序也是一个进程,那么我们也可以在程序中再创建进程。

      多个进程可以实现并发效果,也就是说,当我们的程序中存在多个进程的时候,在某些时候,就会让程序的执行速度变快。

    2.1 multiprocessing模块

      multiprocess不是一个模块而是python中一个操作、管理进程的包。

      大致分为四个部分:创建进程部分,进程同步部分,进程池部分,进程之间数据共享。

      2.1.1 multiprocessing.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.1.2 使用process模块创建进程

    from multiprocessing import Process
    import os
    import time
    
    def func(args,args2):             # 在子进程中执行的
        print(args,args2)
        time.sleep(3)
        print('子进程:',os.getpid())
        print('子进程的子进程:',os.getppid())
        print(12345)
    
    if __name__ == '__main__':
        p = Process(target=func,args = ('参数1','参数2'))  # 注册到进程,p就是一个进程对象,还没有启动进程  (主进程)
        p.start()  # 启动进程(开启一个子进程)
    
        print('*'*10)
        print('父进程:',os.getpid())          # 查看当前进程的进程号
        print('父进程的父进程:',os.getppid())  # 查看当前进程的父进程    (pycharm 的PID)
        print()
    
    # 进程的生命周期
        # 主进程
        # 子进程
        # 开启了子进程的主进程:
            # 如果主进程自己的代码长,肯定先等待自己的代码执行结束,主进程才结束
            # 如果子进程的执行时间长,主进程会在其代码执行完毕之后等待子进程执行完毕后,主进程才结束
    
    # 运行结果:
    **********
    父进程: 4520
    父进程的父进程: 7980
    
    参数1 参数2
    子进程: 11812
    子进程的子进程: 4520
    12345

      2.1.3 进程间的数据隔离

      进程的运行时数据是互相独立的,不会相互影响。

    from multiprocessing import Process
    import time
    
    x = 100
    def change():
        global x
        x = 10
        print('子进程修改了x,变为%s,子进程结束了!'%x)
    
    if __name__ == '__main__':
        p = Process(target=change)
        p.start()
        time.sleep(5)   # 此处主进程进入阻塞状态,进程调度给子进程(用不了5s)
        print(x)
    
    运行结果:
    子进程修改了x,变为10,子进程结束了!
    100
    View Code

      2.1.4 join

      join()方法:优雅的实现父进程等待子进程结束

    from multiprocessing import Process
    import time
    
    def task(n):
        print('这是子进程:{}'.format(n))
        time.sleep(n)
        print('子进程:{}结束了!'.format(n))
    
    if __name__ == '__main__':
        start_time = time.time()
        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('我是主进程')
        print('共耗时:{}'.format(time.time()-start_time))
    
    # 运行结果:
    这是子进程:1
    这是子进程:2
    这是子进程:3
    子进程:1结束了!
    子进程:2结束了!
    子进程:3结束了!
    我是主进程
    共耗时:3.7704484462738037
    View Code

      2.1.5 守护进程

      父进程中将一个子进程设置为守护进程,那么这个子进程会随着主进程的结束而结束。

      主进程创建守护进程

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

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

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

    import os
    import time
    from multiprocessing import Process
    
    class Myprocess(Process):
        def __init__(self,person):
            super().__init__()
            self.person = person
        def run(self):
            print(os.getpid(),self.name)
            print('%s正在和女主播聊天' %self.person)
    
    if __name__ == '__main__':
        p=Myprocess('哪吒')
        p.daemon=True    #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行
        p.start()
        time.sleep(10)   # 在sleep时查看进程id对应的进程ps -ef|grep id
        print('sleep了10s后出现')
    
    #运行结果:
    6852 Myprocess-1
    哪吒正在和女主播聊天
    sleep了10s后出现
    守护进程的启动 
    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()
        time.sleep(0.1)
        print("main-------")#打印该行则主进程代码结束,则守护进程p1应该被终止.#可能会有p1任务执行的打印信息123,因为主进程打印main----时,p1也执行了,但是随即被终止.
    
    # 运行结果:
    main-------
    456
    end456
    主进程代码执行结束守护进程立即结束

      2.1.6 socket聊天并发实例

    from socket import *
    from multiprocessing import Process
    
    server=socket(AF_INET,SOCK_STREAM)
    server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    
    def talk(conn,client_addr):
        while True:
            try:
                msg=conn.recv(1024)
                if not msg:break
                conn.send(msg.upper())
            except Exception:
                break
    
    if __name__ == '__main__': #windows下start进程一定要写到这下面
        while True:
            conn,client_addr=server.accept()
            p=Process(target=talk,args=(conn,client_addr))
            p.start()
    
     使用多进程实现socket聊天并发-server
    使用多进程实现socket聊天并发-server
    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'))
        msg=client.recv(1024)
        print(msg.decode('utf-8'))
    
     client端
    client端

      2.1.7 多进程中的其他方法

    from multiprocessing import Process
    import time
    import random
    
    class Myprocess(Process):
        def __init__(self,person):
            self.name=person
            super().__init__()
    
        def run(self):
            print('%s正在和网红脸聊天' %self.name)
            time.sleep(random.randrange(1,5))
            print('%s还在和网红脸聊天' %self.name)
    
    
    p1=Myprocess('哪吒')
    p1.start()
    
    p1.terminate()#关闭进程,不会立即关闭,所以is_alive立刻查看的结果可能还是存活
    print(p1.is_alive()) #结果为True
    
    print('开始')
    print(p1.is_alive()) #结果为False
    进程对象的其他方法:terminate,is_alive
    from multiprocessing import Process
    import os
    import time
    
    def func(args,args2):             # 在子进程中执行的
        print(args,args2)
        time.sleep(3)
        print('子进程:',os.getpid())
        print('子进程的子进程:',os.getppid())
    
    if __name__ == '__main__':
        p = Process(target=func,args = ('参数1','参数2'))  # 注册到进程,p就是一个进程对象,还没有启动进程  (主进程)
        p.start()  # 启动进程(开启一个子进程)
    
        print('*'*10)
        print('父进程:',os.getpid())          # 查看当前进程的进程号
        print('父进程的父进程:',os.getppid())  # 查看当前进程的父进程    (pycharm 的PID)
        print()
    
    # 运行结果:
    **********
    父进程: 11752
    父进程的父进程: 7980
    
    参数1 参数2
    子进程: 3212
    子进程的子进程: 11752
    进程对象的其他属性:pid和name

    2.2 进程同步控制 —— 锁、信号量、事件

      2.2.1进程锁

      当多个进程使用同一份数据资源的时候,就会因为竞争而引发数据安全或顺序混乱问题。

      (1)互斥锁介绍

    from multiprocessing import Process
    import time
    import random
    
    
    def task1():
        print('这是 task1 任务'.center(30, '-'))
        print('task1 进了洗手间')
        time.sleep(random.randint(1, 3))
        print('task1 办事呢...')
        time.sleep(random.randint(1, 3))
        print('task1 走出了洗手间')
    
    
    def task2():
        print('这是 task2 任务'.center(30, '-'))
        print('task2 进了洗手间')
        time.sleep(random.randint(1, 3))
        print('task2 办事呢...')
        time.sleep(random.randint(1, 3))
        print('task2 走出了洗手间')
    
    
    def task3():
        print('这是 task3 任务'.center(30, '-'))
        print('task3 进了洗手间')
        time.sleep(random.randint(1, 3))
        print('task3 办事呢...')
        time.sleep(random.randint(1, 3))
        print('task3 走出了洗手间')
    
    
    if __name__ == '__main__':
        p1 = Process(target=task1)
        p2 = Process(target=task2)
        p3 = Process(target=task3)
    
        p1.start()
        p2.start()
        p3.start()
    
    # 运行结果:
    ---------这是 task2 任务----------
    task2 进了洗手间
    ---------这是 task1 任务----------
    task1 进了洗手间
    ---------这是 task3 任务----------
    task3 进了洗手间
    task1 办事呢...
    task1 走出了洗手间
    task3 办事呢...
    task2 办事呢...
    task2 走出了洗手间
    task3 走出了洗手间
    进程之间竞争资源
    from multiprocessing import Process, Lock
    import time
    import random
    
    # 生成一个互斥锁
    mutex_lock = Lock()
    
    def task1(lock):
        # 锁门
        lock.acquire()
        print('这是 task1 任务'.center(30, '-'))
        print('task1 进了洗手间')
        time.sleep(random.randint(1, 3))
        print('task1 办事呢...')
        time.sleep(random.randint(1, 3))
        print('task1 走出了洗手间')
        # 释放锁
        lock.release()
    
    
    def task2(lock):
        # 锁门
        lock.acquire()
        print('这是 task2 任务'.center(30, '-'))
        print('task2 进了洗手间')
        time.sleep(random.randint(1, 3))
        print('task2 办事呢...')
        time.sleep(random.randint(1, 3))
        print('task2 走出了洗手间')
        # 释放锁
        lock.release()
    
    
    def task3(lock):
        # 锁门
        lock.acquire()
        print('这是 task3 任务'.center(30, '-'))
        print('task3 进了洗手间')
        time.sleep(random.randint(1, 3))
        print('task3 办事呢...')
        time.sleep(random.randint(1, 3))
        print('task3 走出了洗手间')
        # 释放锁
        lock.release()
    
    
    if __name__ == '__main__':
        p1 = Process(target=task1, args=(mutex_lock, ))
        p2 = Process(target=task2, args=(mutex_lock, ))
        p3 = Process(target=task3, args=(mutex_lock, ))
    
        # 释放新建进程的信号,具体谁先启动无法确定
        p1.start()
        p2.start()
        p3.start()
    
    # 运行结果:
    ---------这是 task2 任务----------
    task2 进了洗手间
    task2 办事呢...
    task2 走出了洗手间
    ---------这是 task1 任务----------
    task1 进了洗手间
    task1 办事呢...
    task1 走出了洗手间
    ---------这是 task3 任务----------
    task3 进了洗手间
    task3 办事呢...
    task3 走出了洗手间
    使用互斥锁解决资源竞争问题

      这种情况虽然使用加锁的形式实现了顺序的执行,但是程序又重新变成串行了,这样确实会浪费了时间,却保证了数据的安全。

      (2) 互斥锁示例

    # 使用互斥锁,保证数据安全。
    import json
    import time
    from multiprocessing import Process,Lock
    
    def show():            # 查票并发
        with open('ticket') as f:
            dic = json.load(f)
        print('余票:%s'%dic['ticket'])
    
    def buy_ticket(i,lock):   # 串行买票
        lock.acquire()          #  拿钥匙进门,其他进程阻塞,直到钥匙还回来   【谁网速快谁就先拿到钥匙】
        with open('ticket') as f:
            dic = json.load(f)
            time.sleep(0.1)
        if dic['ticket'] > 0:
            dic['ticket'] -= 1
            print('33[32m%s买到票了33[0m'%i)
            with open('ticket', 'w') as f:
                dic = json.dump(dic, f)
        else:
            print('33[31m%s没买到票33[0m'%i)
        time.sleep(0.1)
        lock.release()                # 还钥匙
    
    if __name__ == '__main__':
        lock = Lock()
        for i in range(10):
            p1 = Process(target=show)
            p2 = Process(target = buy_ticket,args = (i,lock))
            p1.start()
            p2.start()
    
    
    # 运行结果:
    余票:3
    余票:3
    0买到票了
    余票:2
    2买到票了
    余票:1
    1买到票了
    余票:0
    3没买到票
    余票:0
    4没买到票
    余票:0
    5没买到票
    余票:0
    6没买到票
    余票:0
    7没买到票
    余票:0
    8没买到票
    9没买到票
    互斥锁版抢票

      (3) 思考拓展

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

      2.2.2 信号量

      一个信号量可以使所有的进程都进入阻塞状态,
      也可以控制所有的进程解除阻塞。

    # 一套资源 同一时间 只能被n个人访问
    # 某一段代码 同一时间  只能被n个进程执行
    
    import time
    import random
    from multiprocessing import Process,Semaphore
    
    def ktv(i,sem):
        sem.acquire()  # 获取钥匙
        print('%s走进ktv'%i)
        time.sleep(random.randint(3,5))
        print('%s走出ktv'%i)
        sem.release()
    
    if __name__ == '__main__':
        sem = Semaphore(4)
        for i in range(10):
            p = Process(target = ktv,args = (i,sem))
            p.start()
    
    # 先进去4个,然后出一个才能进一个
    #运行结果:
    0走进ktv
    3走出ktv
    1走进ktv
    7走出ktv
    4走进ktv
    6走出ktv
    5走进ktv
    0走出ktv
    8走进ktv
    1走出ktv
    9走进ktv
    5走出ktv
    4走出ktv
    8走出ktv
    9走出ktv
    信号量示例

      2.2.3 事件

      一个事件被创建之后,默认是阻塞状态

    ###### 基础语法
    # set 和 clear
        # 分别用来修改一个事件的在状态True(不阻塞)或False(阻塞)
    # is_set:用来查看一个事件的状态,默认被设置成阻塞
    # wait:依据事件的状态来决定自己是否阻塞  【在等待某一件事中的一个信号变为True】
    
    from multiprocessing import Process
    from multiprocessing import Event
    
    e = Event()            # 默认被设置成阻塞
    print(e.is_set())      # 查看一个事件的状态,默认被设置成阻塞
    e.set()                # 将一个事件的状态改为True
    print(e.is_set())
    e.wait()               # 是依据e.is_set()的值来决定是否阻塞
    print(12345)
    
    e.clear()              # 将一个事件的状态改为False
    print(e.is_set())      # False
    e.wait()               # 因为上边事件的状态为False,所以认为自己是阻塞状态,所以后边的代码不执行
    print('*'*10)
    事件语法
    ############# 红绿灯事件  ###############
    import time
    import random
    from multiprocessing import Process,Event
    
    def cars(e,i):
        '''
            if '红灯亮了':  # e.iset() 是false
            '等红灯'
        '''
        if not e.is_set():             # 红灯亮了执行这句
            e.wait()
        else:                          # 绿灯亮了执行这句
            print('33[0;32;40mcar %i 通过33[0m'%i)
    
    def light(e):
        while True:
            if e.is_set():            # 默认为False,所以执行else
                e.clear()             # 设置成False
                print('33[31m红灯亮了33[0m')
            else:
                e.set()     # 设置为True
                print('33[32m绿灯亮了33[0m')
                time.sleep(2)
    
    if __name__ == '__main__':
        e = Event()
        traffic = Process(target = light,args = (e,))
        traffic.start()
        for i in range(10):
            car = Process(target = cars,args=(e,i))
            car.start()
            time.sleep(random.random())
    
    # 运行结果:
    绿灯亮了
    car 0 通过
    car 1 通过
    car 2 通过
    car 3 通过
    红灯亮了
    绿灯亮了
    car 4 通过
    car 6 通过
    car 5 通过
    红灯亮了
    绿灯亮了
    car 7 通过
    car 8 通过
    car 9 通过
    红灯亮了
    绿灯亮了
    红灯亮了
    绿灯亮了
    ...
    红绿灯事件

    2.3 进程间通信 —— 队列和管道

      2.3.1 队列

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

    # Queue([maxsize])   创建共享的进程队列。
        参数 :maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。
    
    #底层队列使用管道和锁定实现。
    
    # 队列方法
    #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
    
    def produce(q):
        q.put('hello')
    
    def consume(q):
        print(q.get())
    
    if __name__ == '__main__':
        q = Queue()
        p = Process(target=produce,args=(q,))
        p.start()
        c = Process(target=consume, args=(q,))      # 通过队列实现 两个子进程之间的通信
        c.start()          #hello
    队列实现IPC
    # 队列 ———— 生产消费者模型 【完美的解决了数据供需不平衡的问题】
    
    import time
    import random
    from multiprocessing import Process,JoinableQueue
    
    # 生产者:进程
    def producer(name,food,q):
        for i in range(4):
            time.sleep(random.randint(1,3))
            f = '%s生产了%s%s'%(name,food,i)
            print(f)
            q.put(f)     # 0,1,2,...,20
        q.join()         # 阻塞,直到一个队列中的数据 全部被处理完毕【意思是:原来包子被客人拿走就行,现在得看着客人把包子吃完才行】
    
    # 消费者:进程
    def consumer(q,name):
        while True:
            food = q.get()
            if food is None:
                print('%s获取到了一个空'%name)
                break
            f = '33[31m%s消费了%s33[0m'%(name,food)
            print(f)
            time.sleep(random.randint(1,3))
            q.task_done()              # 任务做完了,count-1
    
    
    if __name__ == '__main__':
        q = JoinableQueue(20)
        p1 = Process(target = producer,args = ('Egon','包子',q))
        p2 = Process(target=producer, args=('wusir', '饺子', q))
        c1 = Process(target=consumer, args=(q, 'alex'))
        c2 = Process(target=consumer, args=(q, 'jin boss'))
    
        p1.start()
        p2.start()
    
        c1.daemon = True          # 设置完守护进程后:主进程中的代码执行完毕后,子进程自动结束
        c2.daemon = True
        c1.start()
        c2.start()
    
        p1.join()                 # 感知一个进程的结束
        p2.join()
        q.put(None)
        q.put(None)
    
    # 运行结果
    wusir生产了饺子0
    alex消费了wusir生产了饺子0
    wusir生产了饺子1
    jin boss消费了wusir生产了饺子1
    Egon生产了包子0
    alex消费了Egon生产了包子0
    wusir生产了饺子2
    jin boss消费了wusir生产了饺子2
    Egon生产了包子1
    jin boss消费了Egon生产了包子1
    wusir生产了饺子3
    alex消费了wusir生产了饺子3
    Egon生产了包子2
    jin boss消费了Egon生产了包子2
    Egon生产了包子3
    alex消费了Egon生产了包子3
    jin boss获取到了一个空
    alex获取到了一个空
    
    
    # 在消费者这一端:
        # 每次获取一个数据,处理一个数据,发送一个记号【标志一个数据被处理成功】
    # 在生产者这一端:
        # 每一次生产要给数据,且每一次生产的数据都放在队列中,在队列中刻上一个记号
        # 当生产者全部生产完毕后,join信号【已经停止生产数据了,且要等待之前被刻上的记号都被消费完】
        # 当数据都被处理完时,join阻塞结束
    # 整体的执行过程:
        # consumer中把所有的人物消耗完
        # producer端的q.join感知到,停止阻塞
        # 主进程中的p.join结束,主进程中代码结束
    队列实现生产消费者模型

      2.3.2 管道

    # 管道注意关闭 【主进程 和 子进程都得关闭】:
        # 如果是生产者或消费者中都没有使用管道的某个端点,就应将它关闭
        # 这也就说明了为何 生产者中关闭了管道的输出端,要在消费者中关闭管道的输入端
    # pipe 数据不安全性【需要加锁】
        # 管道是进程数据不安全的
        # 加锁来控制操作管道的行为,来避免进程之间争抢数据造成的数据不安全现象
    
    from multiprocessing import Pipe,Process
    
    def func(conn1,conn2):
        conn2.close()
        while True:
            try:
                msg = conn1.recv()
            # if msg is None:break
                print(msg)
            except EOFError:        # 没数据可取的话抛出这个错误
                conn1.close()
                break
    
    if __name__ == '__main__':
        conn1, conn2 = Pipe()
        Process(target = func,args=(conn1,conn2)).start()
        conn1.close()
        for i in range(10):
            conn2.send('chileme')
        conn2.close()
        # conn2.send(None)
    
    # 运行结果:
    chileme
    chileme
    chileme
    chileme
    chileme
    chileme
    chileme
    chileme
    chileme
    chileme
    管道初识
    import time
    import random
    from multiprocessing import Pipe,Process,Lock
    
    def producer(con,pro,name,food):
        con.close()
        for i in range(6):
            time.sleep(random.random())
            f = '%s生产了%s%s'%(name,food,i)
            print(f)
            pro.send(f)
        pro.close()
    
    def consumer(con,pro,name,lock):
        pro.close()
        while True:
            try:
                lock.acquire()
                food = con.recv()
                lock.release()
                print('%s吃了%s'%(name,food))
                time.sleep(random.random())
            except EOFError:
                con.close()
                break
    
    if __name__ == '__main__':
        con,pro = Pipe()
        lock =Lock()
        p = Process(target = producer,args = (con,pro,'egon','ganshui'))
        c1 = Process(target=consumer, args=(con, pro, 'alex',lock))
        c2 = Process(target=consumer, args=(con, pro, 'Boss jin',lock))
        
        p.start()
        c1.start()
        c2.start()
    
        con.close()
        pro.close()
    
    #运行结果:
    egon生产了ganshui0
    Boss jin吃了egon生产了ganshui0
    egon生产了ganshui1
    alex吃了egon生产了ganshui1
    egon生产了ganshui2
    Boss jin吃了egon生产了ganshui2
    egon生产了ganshui3
    alex吃了egon生产了ganshui3
    egon生产了ganshui4
    Boss jin吃了egon生产了ganshui4
    egon生产了ganshui5
    alex吃了egon生产了ganshui5
    管道实现生产者消费者模型

    2.4 进程间的数据共享

      进程间应该尽量避免通信,即便需要通信,也应该选择进程安全的工具来避免加锁带来的问题。

      以后我们会尝试使用数据库来解决现在进程之间的数据共享问题。

    # 进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的
    # 虽然进程间数据独立,但可以通过Manager实现数据共享,事实上Manager的功能远不止于此
    # 进程之间应该尽量避免通信,即便需要通信,也应该选择进程安全的工具来避免加锁带来的问题
    # 以后可以尝试使用数据库来解决现在进程之间的数据共享问题
    
    from multiprocessing import Manager,Process,Lock
    
    def main(dic,l):
        l.acquire()
        dic['count'] -= 1
        l.release()
    
    if __name__ == '__main__':
        m = Manager()
        l = Lock()
        dic = m.dict({'count':50})
        p_list = []
        for i in range(20):
            p = Process(target = main,args = (dic,l))
            p.start()
    
            p_list.append(p)
            for i in p_list:
                p.join()     #实现父进程等待子进程结束
    
        print('主进程:',dic)
    
    #运行结果:
    主进程: {'count': 30}
    Manager模块示例

    2.5 进程池和multiprocess.Pool模块

      2.5.1 进程池

     开启进程会带来效率的问题:
       # 每次开启进程,开启属于这个进程的内存空间
       # 寄存器 堆栈 文件 这些随着进程都要被创建出来
      # 进程过多,操作系统的调度
     进程池:
       # python中的,此案创建一个属于进程的池子
       # 这个池子指定能存放多少个进程
       # 先将这些进程创建好
     更高级的进程池:
      # 动态变化的控制启用进程数量
    初识进程池
    进程池的回调函数
    import time
    from multiprocessing import Pool,Process
    
    def func(n):
        for i in range(3):
            print(n + 1)
    
    if __name__ == '__main__':
        start = time.time()
    # 进程池实现
        pool = Pool(5)               # 5个进程
        pool.map(func,range(100))    # 100个任务,自带join(),range这里的args必须是可迭代的类型
        t1 = time.time() - start
    
    # 单进程实现
        p_lst = []
        for i in range(100):
            p = Process(target= func,args=(i,))
            p_lst.append(p)
            p.start()
        for  p in p_lst:p.join()
        t2 = time.time() - start
        print(t1,t2)   # 进程池效率:0.8315212726593018    单进程效率:17.656519651412964
    进程池和单进程运行效率比较

      2.5.2 multiprocess.Pool模块

    # Pool([numprocess  [,initializer [, initargs]]]):创建进程池
    
    # 参数:
    1 numprocess:要创建的进程数,如果省略,将默认使用cpu_count()的值
    2 initializer:是每个工作进程启动时要执行的可调用对象,默认为None
    3 initargs:是要传给initializer的参数组
    # 主要方法:
    # p = Pool()
        # p.map(funcname,iterable)      # 默认异步的执行任务,且自带close 和 join;
                                        # 返回值是所有结果的列表[]
    
        # p.apply(func,args=())         # 同步调用的,只有func执行完以后,才会继续向下执行其他代码;
                                        # 返回值就是func的执行完的return
    
        # p.apply_async(func,args=())   # 异步调用(当func被注册进入一个进程以后,程序就继续向下执行),和主进程完全异步,
                                        # 需要手动 先close 和 后join,来保证多进程和主进程代码的同步性
                                        # 返回值:apply_async返回的对象 --> (obj.get())为了用户能从中获取func的返回值
    
        # p.close():关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
    
        # P.jion():等待所有工作进程退出。此方法只能在close()或teminate()之后调用
    import time
    from multiprocessing import Pool
    
    def func(i):
        time.sleep(0.5)
        return i*i
    
    ####### map:全部执行完后 打印
    if __name__ == '__main__':
        p = Pool(5)
        ret = p.map(func,range(10))
        print('map:',ret)
        print()
    
    ####### apply:结果是func的返回值
    if __name__ == '__main__':
        p = Pool(5)
        res_l = []
        for i in range(10):
            res = p.apply(func,args = (i,))  # apply结果是func的返回值
        print('apply: ',res)               # get()阻塞,等着func的最终计算结果
        print()
    
    ####### apply_async:五个五个  打印
    if __name__ == '__main__':
        p = Pool(5)
        res_l = []
        for i in range(10):
            res = p.apply_async(func,args = (i,))
            res_l.append(res)
        for res in res_l:
            print('apply_async:',res.get())
    
    # 运行结果:
    map: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    
    apply:  81
    
    apply_async: 0
    apply_async: 1
    apply_async: 4
    apply_async: 9
    apply_async: 16
    apply_async: 25
    apply_async: 36
    apply_async: 49
    apply_async: 64
    apply_async: 81
    map、apply、apply_async返回值比较

      2.5.3 爬虫实例

    import requests
    from multiprocessing import Pool
    
    def get(url):
        response = requests.get(url)
        if response.status_code == 200:
            return url,response.content.decode('utf-8')
    
    def call_back(args):
        if args != None:
            url,content = args
            print(url,len(content))
    
    if __name__ == '__main__':
        url_lst = [
            'https://www.cnblogs.com/',
            'https://dig.chouti.com',
            'https://www.souhu.com/',
            'https://www.sougou.com/'
        ]
        p = Pool(5)
        for url in url_lst:
            p.apply_async(get,args=(url,),callback=call_back)
        p.close()
        p.join()
    
    #运行结果:
    https://www.cnblogs.com/ 42364
    爬取数据小例子
    import re
    from urllib.request import urlopen
    from multiprocessing import Pool
    
    def get_page(url,pattern):
        response=urlopen(url).read().decode('utf-8')
        return pattern,response
    
    def parse_page(info):
        pattern,page_content=info
        res=re.findall(pattern,page_content)
        for item in res:
            dic={
                'index':item[0].strip(),
                'title':item[1].strip(),
                'actor':item[2].strip(),
                'time':item[3].strip(),
            }
            print(dic)
    
    if __name__ == '__main__':
        regex = r'<dd>.*?<.*?class="board-index.*?>(d+)</i>.*?title="(.*?)".*?class="movie-item-info".*?<p class="star">(.*?)</p>.*?<p class="releasetime">(.*?)</p>'
        pattern1=re.compile(regex,re.S)
    
        url_dic={
            'http://maoyan.com/board/7':pattern1,
        }
    
        p=Pool()
        res_l=[]
        for url,pattern in url_dic.items():
            res=p.apply_async(get_page,args=(url,pattern),callback=parse_page)
            res_l.append(res)
    
        for i in res_l:
            i.get()
    
    # 运行结果:
    {'index': '1', 'title': '波西米亚狂想曲', 'actor': '主演:拉米·马雷克,本·哈迪,约瑟夫·梅泽罗', 'time': '上映时间:2019-03-22'}
    {'index': '2', 'title': '绿皮书', 'actor': '主演:维果·莫腾森,马赫沙拉·阿里,琳达·卡德里尼', 'time': '上映时间:2019-03-01'}
    {'index': '3', 'title': '老师·好', 'actor': '主演:于谦,汤梦佳,王广源', 'time': '上映时间:2019-03-22'}
    {'index': '4', 'title': '调音师', 'actor': '主演:阿尤斯曼·库拉纳,塔布,拉迪卡·艾普特', 'time': '上映时间:2019-04-03'}
    {'index': '5', 'title': '反贪风暴4', 'actor': '主演:古天乐,郑嘉颖,林峯', 'time': '上映时间:2019-04-04'}
    {'index': '6', 'title': '祈祷落幕时', 'actor': '主演:阿部宽,松岛菜菜子,沟端淳平', 'time': '上映时间:2019-04-12'}
    {'index': '7', 'title': '小飞象', 'actor': '主演:科林·法瑞尔,迈克尔·基顿,丹尼·德维托', 'time': '上映时间:2019-03-29'}
    {'index': '8', 'title': '驯龙高手3', 'actor': '主演:杰伊·巴鲁切尔,刘昊然,亚美莉卡·费雷拉', 'time': '上映时间:2019-03-01'}
    {'index': '9', 'title': '阿丽塔:战斗天使', 'actor': '主演:罗莎·萨拉查,克里斯托弗·沃尔兹,基恩·约翰逊', 'time': '上映时间:2019-02-22'}
    {'index': '10', 'title': '地久天长', 'actor': '主演:王景春,咏梅,齐溪', 'time': '上映时间:2019-03-22'}
    爬虫
  • 相关阅读:
    [RxSwift]4.4、Operator
    [RxSwift]4.3.6、ControlProperty
    [RxSwift]4.3.5、Variable (已弃用)
    [RxSwift]4.3.0、Observable & Observer 既是可监听序列也是观察者
    [RxSwift]4.2.2、Binder
    [RxSwift]4.2.1、AnyObserver
    Java重温学习笔记,Java8新特性:接口的默认方法
    Tomcat下,MySQL连接池的配置和使用(Tomcat9,MySQL5.5)
    MyEclipse2020配置JDK1.8及Tomcat9
    Java重温学习笔记,Java7新特性
  • 原文地址:https://www.cnblogs.com/timetellu/p/10702270.html
Copyright © 2020-2023  润新知