深入理解 Python 的多进程编程 (Multiprocessing)

在 Python 中,multiprocessing 模块提供了多进程支持,是处理并发任务的一个核心工具。与多线程不同,多进程可以突破 GIL(Global Interpreter Lock,全局解释器锁)的限制,充分利用多核 CPU 进行并行计算。本文将详细介绍 Python 中的多进程编程,包括其基础用法、进程间通信、同步机制,以及与线程和协程的对比。


一、为什么选择多进程?

1. GIL 的限制

Python 的 GIL 限制了多线程的并行能力,同一时间只能有一个线程执行 Python 字节码。对于 CPU 密集型任务,多线程不能充分利用多核 CPU。

多进程通过创建独立的进程,每个进程拥有独立的 GIL,可以并行执行任务,适合需要大量计算的场景。

2. 适用场景

  • CPU 密集型任务:如科学计算、视频处理、大量数据的复杂运算。
  • 任务隔离需求:每个进程有独立的内存空间,减少了竞争资源的风险。

  • 二、multiprocessing 的基础用法

    1. 创建子进程

    使用 multiprocessing.Process 类可以轻松创建子进程。

    from multiprocessing import Process
    import os
    
    def worker(task_name):
        print(f"Task {task_name} is running in process {os.getpid()}")
    
    if __name__ == "__main__":
        process1 = Process(target=worker, args=("A",))
        process2 = Process(target=worker, args=("B",))
    
        process1.start()
        process2.start()
    
        process1.join()
        process2.join()
    
        print("All processes completed")
    

    输出

    Task A is running in process 12345
    Task B is running in process 12346
    All processes completed
    

    2. 进程池(multiprocessing.Pool

    当需要管理大量进程时,使用进程池(Pool)可以更方便地分配和调度任务。

    from multiprocessing import Pool
    
    def worker(x):
        return x * x
    
    if __name__ == "__main__":
        with Pool(4) as pool:  # 创建包含 4 个进程的进程池
            results = pool.map(worker, range(10))
        print(results)
    

    输出

    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    

    三、进程间通信

    Python 提供了多种方式实现进程间通信,包括队列(Queue)、管道(Pipe)和事件(Event)。

    1. 使用 Queue

    Queue 提供了线程安全的队列,可以实现进程间的数据共享。

    from multiprocessing import Process, Queue
    
    def producer(queue):
        for i in range(5):
            queue.put(i)
            print(f"Produced: {i}")
    
    def consumer(queue):
        while not queue.empty():
            item = queue.get()
            print(f"Consumed: {item}")
    
    if __name__ == "__main__":
        q = Queue()
    
        p1 = Process(target=producer, args=(q,))
        p2 = Process(target=consumer, args=(q,))
    
        p1.start()
        p1.join()
    
        p2.start()
        p2.join()
    

    输出

    Produced: 0
    Produced: 1
    Produced: 2
    Produced: 3
    Produced: 4
    Consumed: 0
    Consumed: 1
    Consumed: 2
    Consumed: 3
    Consumed: 4
    

    2. 使用 Pipe

    Pipe 提供了双向通信的能力。

    from multiprocessing import Process, Pipe
    
    def sender(pipe):
        for i in range(5):
            pipe.send(i)
            print(f"Sent: {i}")
        pipe.close()
    
    def receiver(pipe):
        while True:
            try:
                item = pipe.recv()
                print(f"Received: {item}")
            except EOFError:
                break
    
    if __name__ == "__main__":
        parent_conn, child_conn = Pipe()
    
        p1 = Process(target=sender, args=(parent_conn,))
        p2 = Process(target=receiver, args=(child_conn,))
    
        p1.start()
        p2.start()
    
        p1.join()
        p2.join()
    

    输出

    Sent: 0
    Sent: 1
    Sent: 2
    Sent: 3
    Sent: 4
    Received: 0
    Received: 1
    Received: 2
    Received: 3
    Received: 4
    

    四、进程同步机制

    多进程共享资源时需要同步工具,multiprocessing 提供了 LockEvent 等工具。

    1. 使用 Lock

    Lock 用于防止多个进程同时访问共享资源。

    from multiprocessing import Process, Lock
    
    counter = 0
    
    def worker(lock):
        global counter
        for _ in range(100000):
            with lock:
                counter += 1
    
    if __name__ == "__main__":
        lock = Lock()
    
        p1 = Process(target=worker, args=(lock,))
        p2 = Process(target=worker, args=(lock,))
    
        p1.start()
        p2.start()
    
        p1.join()
        p2.join()
    
        print(f"Final counter value: {counter}")
    

    2. 使用 Event

    Event 是一种简单的线程同步原语,用于让一个进程等待另一个进程发出信号。

    from multiprocessing import Process, Event
    
    def worker(event):
        print("Worker waiting for event to be set...")
        event.wait()  # 等待事件被设置
        print("Worker received event signal, starting work!")
    
    if __name__ == "__main__":
        event = Event()
    
        p = Process(target=worker, args=(event,))
        p.start()
    
        print("Main process performing some setup...")
        import time
        time.sleep(2)
    
        print("Main process setting event.")
        event.set()  # 触发事件
    
        p.join()
    

    输出

    Worker waiting for event to be set...
    Main process performing some setup...
    Main process setting event.
    Worker received event signal, starting work!
    

    五、多进程、线程与协程的对比

    特性 多进程(multiprocessing 多线程(threading 协程(asyncio
    适用场景 CPU 密集型任务 I/O 密集型任务,简单并发 I/O 密集型任务,高性能并发
    多核支持 可充分利用多核 受限于 GIL,无多核支持 单线程实现并发,无多核支持
    资源隔离 每个进程独立,资源隔离性高 线程共享内存,隔离性较低 协程运行在同一线程,隔离性低
    资源开销 进程上下文切换开销高 线程上下文切换开销较低 协程更轻量,占用资源更少
    通信方式 队列(Queue)、管道(Pipe 共享内存或队列 事件循环或 asyncio.Queue
    编程难度 相对复杂 较简单 语法直观,使用方便

    六、总结与推荐

    1. 选择多进程

    2. 当任务是 CPU 密集型,需要并行处理时,优先考虑 multiprocessing
    3. 适合需要进程隔离的场景,避免共享资源引发的数据竞争。
    4. 选择多线程

    5. 适用于 I/O 密集型任务,例如文件操作、网络请求。
    6. 如果任务需要共享内存并发处理,多线程更方便。
    7. 选择协程

    8. 在高并发的 I/O 密集型任务 中(如异步网络请求),协程是最优选择。
    9. 轻量、性能高,适合现代异步编程。

    通过合理选择工具,可以在 Python 中充分利用多进程、多线程和协程的优势,打造高性能的并发程序。

    作者:花千树-010

    物联沃分享整理
    物联沃-IOTWORD物联网 » 深入理解 Python 的多进程编程 (Multiprocessing)

    发表回复