9. 使用 Pipe 实现进程通信

Pipe 就像一根“水管”,一头负责发送,另一头负责接收。

 1️⃣ 创建管道

# Pipe() 会返回两个连接对象,它们分别代表管道的两端。
# duplex用于控制管道为单向还是双向,True表示双向,False表示单向
con1, con2 = Pipe(duplex=True)

# 单向 Pipe 的规则:con1只能发送,con2只能接收。

2️⃣发送与接收

  • send方法: 向管道中发送数据。
  • recv方法: 从管道中接收数据。

3️⃣测试代码:

import time
from multiprocessing import Process, Pipe

def test1(con1):
    time.sleep(2)
    con1.send(100)
    print('test1发送了100')

def test2(con2):
    data = con2.recv()
    print(f'test2接收了{data}')


if __name__ == '__main__':
    con1, con2 = Pipe(duplex=False)
    p1 = Process(target=test1, args=(con1,))
    p2 = Process(target=test2, args=(con2,))

    p1.start()
    p2.start()

    p1.join()
    p2.join()

    10. 继承 Process 类创建进程

    当子进程逻辑比较复杂,或者想把「进程 + 行为」封装成一个整体时,可以使用继承 Process 类的方式去创建进程,这种方式属于“面向对象风格”

    1️⃣核心要点:

    • 必须继承 Process类
    • 把子进程要干的事,写进 run() 方法里
    • 依然使用start方法启动进程,不要手动调用 run()!
    • 若子进程不需要参数,可以不写__init__,若需要参数,则需编写__init__
    • 传给的子进程的参数,作为实例属性保存 。

    2️⃣示例代码:

    from multiprocessing import Process
    import os, time
    
    class SpeakProcess(Process):
        def __init__(self, a, b, **kwargs):
            super().__init__(**kwargs)
            self.a = a
            self.b = b
    
        def run(self):
            for index in range(10):
                print(f'{self.a}--{self.b}--我在说话{index}, 进程pid是:{os.getpid()}, 我的父进程是:{os.getppid()}')
                time.sleep(1)
    
    class StudyProcess(Process):
        def run(self):
            for index in range(15):
                print(f'我在学习{index}, 进程pid是:{os.getpid()}, 我的父进程是:{os.getppid()}')
                time.sleep(1)
    
    if __name__ == '__main__':
        print('我是主进程中的【第一行】打印')
        p1 = SpeakProcess(100, 200)
        p2 = StudyProcess()
    
        p1.start()
        p2.start()
    
        p1.join()
        p2.join()
    
        print('我是主进程中的【最后一行】打印')

    11. 进程池(ProcessPoolExecutor)

    截至目前,我们已经学会了创建多个进程一起工作,但是我们仍然面临一个问题:如果每来一个任务就创建一个进程,会很浪费系统资源,因为进程创建 / 销毁成本很高,当有大量任务时,系统资源浪费严重。

    使用进程池的优势:如何使用进程池统一管理多个子进程,避免频繁创建 / 销毁进程带来的开销,因为进程池会提前创建固定数量的进程,反复使用它们来执行任务。

    1️⃣创建『进程池执行器』、使用 submit 方法提交任务、使用 shutdown 方法等待任务完成。

    def work(n):
        print(f'work正在执行任务{n}.........{os.getpid()}')
        time.sleep(1)
    
    if __name__ == '__main__':
        print('---------start-------------')
        # 创建一个进程池执行器
        executor = ProcessPoolExecutor(3)
        # 使用 submit 方法提交任务(submit 只负责“提交任务”,不会阻塞主进程)
        executor.submit(work, 1)
        executor.submit(work, 2)
        executor.submit(work, 3)
        executor.submit(work, 4)
        executor.submit(work, 5)
        executor.submit(work, 6)
        executor.submit(work, 7)
        # shutdown 的作用:不再接收新的任务。
        # wait=True 的作用:阻塞主进程,等待进程池中所有任务执行完毕。
        executor.shutdown(wait=True)
        print('---------end-------------')
    

    2️⃣获取子进程执行后的返回结果(Future类的实例对象 + result方法)

    def work(n):
        print(f'work正在执行任务{n}.........{os.getpid()}')
        time.sleep(1)
        return f'我是任务{n}的结果'
    
    if __name__ == '__main__':
        print('---------start-------------')
        # 创建一个进程池执行器
        executor = ProcessPoolExecutor(3)
        # 使用 submit 方法提交任务(submit 只负责“提交任务”,不会阻塞主进程)
        # f1 = executor.submit(work, 1)
        # f2 = executor.submit(work, 2)
        # f3 = executor.submit(work, 3)
        # f4 = executor.submit(work, 4)
        # f5 = executor.submit(work, 5)
        # f6 = executor.submit(work, 6)
        # f7 = executor.submit(work, 7)
        futures = [executor.submit(work, index) for index in range(1, 8)]
        # 阻塞主进程,等待进程池中所有任务执行完毕。
        executor.shutdown(wait=True)
        # print(f1.result())
        # print(f2.result())
        # print(f3.result())
        # print(f4.result())
        # print(f5.result())
        # print(f6.result())
        # print(f7.result())
        for f in futures:
            print(f.result())
        print('---------end-------------')

    3️⃣使用 as_completed:按“完成顺序”获取结果

    def work(n):
        print(f'work正在执行任务{n}.........{os.getpid()}')
        if n == 1:
            time.sleep(15)
        elif n == 2:
            time.sleep(10)
        else:
            time.sleep(1)
        return f'我是任务{n}的结果'
    
    if __name__ == '__main__':
        print('---------start-------------')
        # 创建一个进程池执行器
        executor = ProcessPoolExecutor(3)
        # 使用 submit 方法提交任务(submit 只负责“提交任务”,不会阻塞主进程)
        futures = [executor.submit(work, index) for index in range(1, 8)]
        # 准备一个 result_list 去收集任务的具体结果
        result_list = []
        # 收集每个任务的结果
        for f in as_completed(futures):
            result_list.append(f.result())
        # 阻塞主进程,等待进程池中所有任务执行完毕。
        executor.shutdown(wait=True)
        # 打印最终的结
        print(result_list)
        print('---------end-------------')

    4️⃣使用 add_done_callback 方法,为任务添加完成时的回调函数。

    def work(n):
        print(f'work正在执行任务{n}.........{os.getpid()}')
        if n == 1:
            time.sleep(15)
        elif n == 2:
            time.sleep(10)
        else:
            time.sleep(1)
        return f'我是任务{n}的结果'
    
    if __name__ == '__main__':
        print('---------start-------------')
        # 创建一个进程池执行器
        executor = ProcessPoolExecutor(3)
        # 准备一个 result_list 列表去收集任务的结果
        result_list = []
        # 任务完成后的回调函数
        def done_func(futrue):
            result_list.append(futrue.result())
        # 开启7个任务,并指定回调函数
        for index in range(1, 8):
            f = executor.submit(work, index)
            f.add_done_callback(done_func)
        # 等所有任务都完成
        executor.shutdown(wait=True)
        # 打印最终的结果(按“完成的顺序”获取)
        print(result_list)
        print('---------end-------------')

    5️⃣️使用 map 方法批量提交任务(注意:map方法本身不阻塞,但读取其返回的生成器对象是阻塞的,并且得到结果的顺序,与任务分配的顺序是一致的)

    map方法会把这一批任务提交到进程池里执行,它会立刻返回一个生成器,真正的阻塞发生在:生成器取结果时,如 list(result)

    def work(n):
        print(f'work正在执行任务{n}.........{os.getpid()}')
        if n == 1:
            time.sleep(15)
        elif n == 2:
            time.sleep(10)
        else:
            time.sleep(1)
        return f'我是任务{n}的结果'
    
    if __name__ == '__main__':
        print('---------start-------------')
        # 创建一个进程池执行器
        executor = ProcessPoolExecutor(3)
        # 通过 map 方法批量提交任务(结果按照提交的顺序来)
        results = executor.map(work, [1, 2, 3, 4, 5, 6, 7])
        # 获取 results 生成器中的内容
        print(list(results))
        # 等所有任务都完成
        executor.shutdown(wait=True)
        print('---------end-------------')

    6️⃣使用 with:进程池的“自动回收”写法,离开 with 代码块时自动执行 shutdown(wait=True)

    def work(n):
        print(f'work正在执行任务{n}.........{os.getpid()}')
        if n == 1:
            time.sleep(15)
        elif n == 2:
            time.sleep(10)
        else:
            time.sleep(1)
        return f'我是任务{n}的结果'
    
    if __name__ == '__main__':
        print('---------start-------------')
        # 创建一个进程池执行器
        with ProcessPoolExecutor(3) as executor:
            # 通过 map 方法批量提交任务(结果按照提交的顺序来)
            results = executor.map(work, [1, 2, 3, 4, 5, 6, 7])
            # 获取 results 生成器中的内容
            print(list(results))
        print('---------end-------------')
    © 版权声明
    THE END
    喜欢就支持一下吧
    点赞8 分享
    评论 抢沙发
    头像-大赛人网
    欢迎您留下宝贵的见解!
    提交
    头像-大赛人网

    昵称

    取消
    昵称

      请登录后查看评论内容