python 的asyncio与multiporcess

在 Python 中,协程(asyncio)、多线程(multithreading)和多进程(multiprocessing)是实现并发和并行的三种主要方式。它们各有优劣,适用于不同场景。以下是它们的比较、使用实例和适用场景。


🔄 一、概述

1. 协程(Coroutines)

  • 基于事件循环asyncio)。
  • 协作式多任务:协程通过 await 关键字主动让出控制权。
  • 轻量级:比线程更轻,适合高并发 I/O 操作。
  • 需要异步库支持(如 aiohttp, asyncpg)。

2. 多线程(Multithreading)

  • 抢占式多任务:由操作系统调度。
  • 共享内存:线程间通信简单,但容易引发竞争条件。
  • 受 GIL(全局解释器锁)限制:Python 中线程无法真正并行执行 CPU 密集型任务。

3. 多进程(Multiprocessing)

  • 独立内存空间:每个进程拥有独立的 Python 解释器和内存。
  • 绕过 GIL:适合 CPU 密集型任务。
  • 进程间通信复杂:需要使用 Queue, PipeManager

📊 二、对比表

特性 协程(asyncio 多线程(threading 多进程(multiprocessing
并发模型 协作式(await 抢占式(OS 调度) 抢占式(OS 调度)
并行性 单线程,无法并行 单线程(受 GIL 限制) 多线程,可并行
内存占用 极低(轻量) 中等(共享内存) 高(独立内存)
适合场景 I/O 密集型(如网络请求) I/O 密集型(少量线程) CPU 密集型
通信/同步 asyncio.Queue threading.Lock, threading.Event Queue, Pipe, Manager
异步依赖 需要异步库(如 aiohttp 可使用同步库 可使用同步库
线程/进程数 可支持数万协程 通常数百线程 通常几十进程

🧪 三、使用实例

1. 协程(asyncio)示例

import asyncio

async def fetch(url):
    print(f"Fetching {url}")
    await asyncio.sleep(1)
    print(f"Done {url}")

async def main():
    tasks = [fetch(f"URL-{i}") for i in range(5)]
    await asyncio.gather(*tasks)

asyncio.run(main())

2. 多线程(threading)示例

import threading
import time

def task(name):
    time.sleep(1)
    print(f"Thread {name} done")

threads = [threading.Thread(target=task, args=(i,)) for i in range(5)]
for t in threads:
    t.start()
for t in threads:
    t.join()

3. 多进程(multiprocessing)示例

from multiprocessing import Process
import time

def task(name):
    time.sleep(1)
    print(f"Process {name} done")

if __name__ == "__main__":
    processes = [Process(target=task, args=(i,)) for i in range(5)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()

🎯 四、适用场景

场景 推荐方式 说明
I/O 密集型(大量并发) asyncio 协程轻量,适合高并发网络请求、异步任务。
I/O 密集型(少量线程) threading 简单易用,适合少量线程的并发 I/O 操作。
CPU 密集型(并行计算) multiprocessing 绕过 GIL,适合图像处理、科学计算等任务。
GUI 应用 + 后台任务 threadingasyncio threading 更适合传统 GUI 框架,asyncio 更适合现代异步框架(如 PyQt5)。
分布式任务调度 multiprocessing + concurrent.futures 适合构建分布式计算框架。

⚠️ 五、潜在问题

问题 说明
协程阻塞事件循环 若在协程中调用 time.sleep 而非 asyncio.sleep,会阻塞整个事件循环。
线程竞争条件 多线程共享内存,需使用锁(Lock, Semaphore)来避免资源竞争。
进程间通信复杂 multiprocessing.QueueManager 可用于跨进程通信,但性能较低。
GIL 限制 threading 受 GIL 影响,无法真正并行执行 CPU 密集型任务。

📌 六、总结

选择建议 说明
高并发 I/O 操作(如 Web 爬虫、API 调用) 优先使用 asyncio
少量线程的并发 I/O(如 GUI 后台任务) 使用 threading
CPU 密集型任务(如图像处理、机器学习) 使用 multiprocessing
需要异步编程模型 使用 asyncio,但需确保依赖库支持异步。

在 Python 的多进程编程中,multiprocessing.QueueManagerconcurrent.futures.ProcessPoolExecutor 是三种常用的工具,用于实现进程间通信、共享状态和任务并行执行。它们各自适用于不同的场景,下面将分别展示使用这些工具的示例,并说明其适用场景。


🧱 一、使用 multiprocessing.Queue 实现进程间通信

multiprocessing.Queue 是一个线程和进程安全的队列,适合用于实现生产者-消费者模型,即一个进程生成任务,多个进程消费任务。

示例:任务队列

from multiprocessing import Process, Queue
import time

def worker(queue):
    while True:
        task = queue.get()
        if task is None:  # 任务结束标识
            break
        print(f"Processing: {task}")
        time.sleep(0.5)

def main():
    queue = Queue()

    # 启动多个工作进程
    workers = [Process(target=worker, args=(queue,)) for _ in range(3)]
    for w in workers:
        w.start()

    # 添加任务
    for i in range(10):
        queue.put(f"Task-{i}")

    # 发送结束信号
    for _ in workers:
        queue.put(None)

    # 等待所有工作进程完成
    for w in workers:
        w.join()

if __name__ == "__main__":
    main()

说明:

  • worker() 是消费者函数,不断从队列中取出任务。
  • queue.get() 是阻塞的,直到有任务到来或遇到 None
  • 适用于任务队列、任务分发等场景。

🌐 二、使用 Manager 实现共享状态

Manager 提供了一个服务器进程,允许在多个进程中共享对象(如列表、字典、值等),适用于需要共享状态的场景。

示例:共享状态管理

from multiprocessing import Process, Manager
import time

def worker(shared_dict, name):
    shared_dict[name] = f"Processed by {name}"
    time.sleep(0.5)

def main():
    with Manager() as manager:
        shared_dict = manager.dict()  # 创建共享字典

        processes = []
        for i in range(5):
            p = Process(target=worker, args=(shared_dict, f"Worker-{i}"))
            processes.append(p)
            p.start()

        for p in processes:
            p.join()

        print("Shared Dict:", shared_dict)

if __name__ == "__main__":
    main()

说明:

  • 使用 manager.dict() 创建共享字典。
  • 每个进程可以修改共享字典。
  • 适用于共享状态、数据共享等场景。

🧠 三、使用 concurrent.futures.ProcessPoolExecutor 并行执行任务

ProcessPoolExecutorconcurrent.futures 模块中用于管理进程池的高级接口,适合用于并行计算密集型任务。

示例:并行计算平方数

from concurrent.futures import ProcessPoolExecutor
import time

def square(x):
    time.sleep(0.5)
    return x * x

def main():
    with ProcessPoolExecutor(max_workers=4) as executor:
        results = executor.map(square, range(10))
        for result in results:
            print(f"Result: {result}")

if __name__ == "__main__":
    main()

说明:

  • executor.map() 将任务分发给进程池。
  • 适用于批量任务并行处理、CPU 密集型任务
  • 无需手动管理进程和队列。

🧩 四、综合示例:构建一个简易的 Worker 集群

结合 QueueManager,我们可以实现一个简单的 Worker 集群模型,主进程分发任务,多个 Worker 进程处理任务,并通过共享状态收集结果。

from multiprocessing import Process, Queue, Manager
import time

def worker(queue, results):
    while True:
        task = queue.get()
        if task is None:
            break
        print(f"Processing: {task}")
        results[task] = task * task
        time.sleep(0.5)

def main():
    with Manager() as manager:
        queue = Queue()
        results = manager.dict()

        # 启动多个工作进程
        workers = [Process(target=worker, args=(queue, results)) for _ in range(3)]
        for w in workers:
            w.start()

        # 添加任务
        for i in range(10):
            queue.put(i)

        # 发送结束信号
        for _ in workers:
            queue.put(None)

        # 等待所有工作进程完成
        for w in workers:
            w.join()

        print("Final Results:", results)

if __name__ == "__main__":
    main()

📌 五、总结与适用场景

工具 适用场景 特点
multiprocessing.Queue 任务队列、生产者-消费者模型 简单、高效、适用于任务分发
Manager 共享状态、数据共享 支持字典、列表等共享对象,适合状态同步
ProcessPoolExecutor 并行执行函数、批量计算任务 高级接口,适合 CPU 密集型任务并行处理

📚 参考资料

通过以上示例,你可以根据实际需求选择合适的工具来构建多进程应用。

posted @ 2025-06-03 22:52  iTech  阅读(174)  评论(0)    收藏  举报