【并行计算】Python concurrent ——高效灵活并行计算的利剑


Python concurrent ——高效灵活并行计算的利剑

一、理解并发、并行与 concurrent 的角色

  1. 为什么需要并发?
    现代计算机系统包含多个 CPU 核心,但许多程序是单线程执行的,导致 CPU 资源浪费。并发编程允许程序在等待 I/O 操作(如网络请求、文件读写)时,利用 CPU 执行其他任务,从而提高整体效率。
  2. 关键概念区分:
    以下是关于并发、并行、同步、异步的概念总结表格:
概念定义特点/实现方式适用场景
并发 (Concurrency)同时处理多个任务的能力任务可交替执行(单CPU核心通过快速切换实现),给人“同时进行”的感觉I/O密集型任务
并行 (Parallelism)同时执行多个任务需要多核CPU,任务真正在同一时刻运行CPU密集型任务
同步 (Synchronous)任务按顺序执行一个任务完成后才开始下一个任务任务间有强依赖关系时
异步 (Asynchronous)任务在等待时可让出控制权,执行其他任务等待操作完成后再回来处理结果,concurrent.futures 提供基于线程/进程的同步接口管理异步操作存在等待操作(如I/O)的场景
  1. I/O 密集型任务 🆚 CPU 密集型任务
  • I/O 密集型任务: 网络请求(爬虫、API 调用)、文件读写、数据库查询。这些操作大部分时间花在等待网络传输或磁盘响应上,而不是 CPU 计算。
  • CPU 密集型任务: 大量数学计算、图像处理、复杂算法。这些任务会持续占用 CPU。
    目标: 提高程序的整体吞吐量和响应速度,更有效地利用系统资源。
  1. concurrent 模块是什么?
    • concurrent.futures 是 Python 标准库中提供的一个高级并发编程模块,旨在简化异步任务的执行。它为开发者提供了一个统一的接口来管理线程池和进程池,使并发编程更加简单、可维护。
    • 它的核心是 concurrent.futures 子模块,提供了 ThreadPoolExecutorProcessPoolExecutor 两个主要的类。
    • 它的核心价值是:
      • 不用直接管理线程 (threading.Thread) 或进程 (multiprocessing.Process) 的创建、启动、通信和清理。
      • 提供了统一的接口 (submit, map, as_completed) 来提交任务和获取结果。
      • 内置了对任务结果 (Future 对象) 的管理机制。

二、concurrent.futures 深度解析

concurrent.futuresconcurrent 包中实际使用的部分。主要有学习两个执行器:

  • ThreadPoolExecutor: 线程池。
  • ProcessPoolExecutor: 进程池。

它们都实现了 Executor 接口,因此 API 高度一致。

(一) Future 对象 - 并发的基石

1. Future定义
  • 当你向执行器提交一个任务时,submit() 方法返回一个 Future 对象。这个对象代表了一个尚未完成的计算
2. Future作用
具体说明
占位符作为未来某个时刻才会产生的结果的临时替代(占位)
状态查询可检查任务是否完成、是否被取消、是否抛出异常,掌握任务生命周期状态
结果获取通过 result() 方法获取任务最终结果,或通过 exception() 方法获取异常
回调注册任务完成后,自动调用预先指定的函数(回调函数),无需主动轮询结果
3. Future 的状态机
状态名称描述说明
PENDING任务已提交,但尚未开始执行
RUNNING任务正在执行中
FINISHED任务已完成(包括成功完成、执行失败或被取消)
4. Future 的关键方法
方法名称返回值类型描述说明
future.done()bool检查任务是否完成(无论成功、失败或被取消)
future.cancelled()bool检查任务是否被成功取消
future.running()bool检查任务是否正在运行
future.result(timeout=None)Any获取任务结果。若任务未完成,会阻塞至任务完成或超时;如果任务抛出异常,result() 会重新抛出该异常。
future.exception(timeout=None)Exception or None获取任务抛出的异常。若任务成功,返回None;若任务未完成,会阻塞至任务完成或超时
future.add_done_callback(fn)注册回调函数fn,当Future状态变为FINISHED时,自动调用fn(future)
注意:回调执行时机不确定(可能在主线程或工作线程/进程),应避免耗时操作或修改共享状态
5. Future基本用法
from concurrent.futures import Future

# 创建一个 Future 对象
f = Future()

# 模拟一个异步操作
def set_result():
    f.set_result("Hello, Future!")

# 在另一个线程中设置结果
import threading
threading.Thread(target=set_result).start()

# 等待结果(阻塞)
print(f.result())  # 输出: Hello, Future!

(二)Executor 基类

1. Executor 定义

Executor 是一个抽象基类,定义了提交任务和获取结果的标准接口。ThreadPoolExecutorProcessPoolExecutor 都实现了这个接口。

2. Executor的关键方法
方法名称核心功能关键特性适用场景
executor.submit(fn, /, *args, **kwargs)将可调用对象 fn 及其参数提交给执行器,调度任务执行1. 立即返回 Future 对象,可后续查询状态/获取结果
2. 支持单个任务的灵活提交
提交单个、参数不规则或独立的任务
executor.map(func, *iterables, timeout=None, chunksize=1)并发版 map,将 func 批量应用于 *iterables 的元素1. 类似 zip(*iterables) 匹配元素,按输入顺序返回结果
2. 支持超时设置,超时抛 TimeoutError
3. chunksize 优化进程池 IPC 开销(对线程池无效)
4. 遇第一个异常即停止
批量、同构(任务逻辑/参数结构一致)的任务
executor.shutdown(wait=True, *, cancel_futures=False)清理执行器资源(线程/进程)1. with 语句会自动调用,无需手动执行
2. wait=True:阻塞至所有任务完成;wait=False:立即返回,后台续跑任务
3. cancel_futures=True(Python 3.9+):取消所有待处理任务(不影响运行中任务)
执行器不再使用时,释放资源

executor.submit(fn, /, *args, **kwargs) 示例代码:

 import concurrent.futures
 import time

 def slow_task(n):
     print(f"Task {n} starting...")
     time.sleep(2)
     print(f"Task {n} finished.")
     return n * n

 # 创建一个最多2个线程的线程池
 with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
     # 提交任务,得到 Future 对象
     future1 = executor.submit(slow_task, 1)
     future2 = executor.submit(slow_task, 2)
     future3 = executor.submit(slow_task, 3)

     # 立刻打印 Future 对象,此时任务可能还没开始或还在运行
     print(future1) # <Future at 0x... state=pending>
     print(future2) # <Future at 0x... state=pending>

     # 获取结果(会阻塞)
     result1 = future1.result()
     result2 = future2.result()
     result3 = future3.result()

     print(f"Results: {result1}, {result2}, {result3}")

executor.map(func, *iterables, timeout=None, chunksize=1) 示例代码:

 import concurrent.futures
 import requests

 def fetch_url(url):
     response = requests.get(url)
     return len(response.content)

 urls = [
     'https://httpbin.org/delay/1',
     'https://httpbin.org/delay/2',
     'https://httpbin.org/delay/1'
 ]

 with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
     # map 会并发执行 fetch_url,但结果按 urls 的顺序返回
     results = executor.map(fetch_url, urls, timeout=10)
     for url, length in zip(urls, results):
         print(f"{url}: {length} bytes")
3. 两种主要执行器:线程池 🆚 进程池
ThreadPoolExecutor(线程池)ProcessPoolExecutor(进程池)
核心原理复用预先创建的线程执行任务,通过线程切换模拟“同时处理”,共享主线程内存空间。复用预先创建的子进程执行任务,每个进程有独立解释器和内存,绕过 GIL 实现真正并行。
适用场景I/O 密集型任务(网络请求、文件操作、数据库访问等)—— I/O 等待时 GIL 释放,线程切换开销小。CPU 密集型任务(科学计算、图像处理、大数据分析等)—— 充分利用多核 CPU,无 GIL 限制。
关键构造参数- max_workers:默认 min(32, os.cpu_count() + 4)
- thread_name_prefix:线程名称前缀(调试用)
- initializer/initargs:线程启动时的初始化函数及参数
- max_workers:默认 os.cpu_count()(通常设为 CPU 核心数)
- mp_context:进程创建上下文(fork/spawn/forkserver
- initializer/initargs:进程启动时的初始化函数及参数
核心优点1. 轻量级,线程创建/切换开销小
2. 线程间共享内存,数据交换方便
1. 绕过 GIL,支持真正并行计算
2. 进程内存隔离,无线程安全问题(如竞态条件)
3. 单个进程崩溃不影响主进程及其他进程
核心缺点1. 受 GIL 限制,纯 CPU 密集型任务性能提升有限
2. 共享内存易引发线程安全问题,需额外同步(如 threading.Lock
1. 进程创建/切换开销大
2. 进程间通信(IPC)成本高(依赖 pickle 序列化)
3. 内存不共享,需用 multiprocessing 工具传递数据

ThreadPoolExecutor构造函数参数:

ThreadPoolExecutor(
    max_workers=None, 
    thread_name_prefix='',
    initializer=None,
    initargs=()
)
  • max_workers: 线程池大小(默认 min(32, os.cpu_count() + 4))
  • thread_name_prefix: 线程名称前缀
  • initializer, initargs: 每个线程启动时调用的初始化函数

ThreadPoolExecutor示例:

import time
from concurrent.futures import ThreadPoolExecutor

def task(n):
   print(f"Task {n} started")
   time.sleep(n)
   print(f"Task {n} completed")
   return n * n

# 使用 with 语句自动管理资源
with ThreadPoolExecutor(max_workers=3) as executor:
   # 提交任务,获取 Future 对象
   future1 = executor.submit(task, 2)
   future2 = executor.submit(task, 1)
   future3 = executor.submit(task, 3)
   
   # 获取结果(阻塞)
   print(future1.result())  # 4
   print(future2.result())  # 1
   print(future3.result())  # 9

ProcessPoolExecutor构造函数参数

ProcessPoolExecutor(
    max_workers=None,
    mp_context=None,
    initializer=None,
    initargs=()
)
  • max_workers: 进程池大小(默认 os.cpu_count())
  • mp_context: multiprocessing 上下文(‘fork’, ‘spawn’, ‘forkserver’)
  • initializer, initargs: 每个进程启动时调用的初始化函数
  • ProcessPoolExecutor示例(CPU 密集型):
示例:进程池基础用法
import math
from concurrent.futures import ProcessPoolExecutor

def is_prime(n):
    if n < 2:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False
    sqrt_n = int(math.isqrt(n))
    for i in range(3, sqrt_n + 1, 2):
        if n % i == 0:
            return False
    return True

if __name__ == '__main__':  # Windows 需要这个
    numbers = [112272535095293, 112582705942171, 112272535095293]
    
    with ProcessPoolExecutor() as executor:
        results = executor.map(is_prime, numbers)
        for number, prime in zip(numbers, results):
            print(f"{number} is prime: {prime}")

三、 高效与安全的并发编程

(一)concurrent.futures.as_completed(): 处理最先完成的任务

  • 问题: executor.map() 按输入顺序返回结果。如果你希望一旦有任务完成就立即处理其结果,而不必等待前面慢的任务,该怎么办?
  • 解决方案: as_completed(fs, timeout=None)
    • 接收一个 Future 对象的可迭代集合 fs
    • 返回一个迭代器,该迭代器按 Future 完成的顺序(而不是提交的顺序)产生 Future 对象。
    • 非常适合“谁先完成就处理谁”的场景。
    import concurrent.futures
    import random
    import time
    
    def task_with_random_delay(name):
        delay = random.uniform(1, 5)
        time.sleep(delay)
        return f"Task {name} completed after {delay:.2f}s"
    
    with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
        # 提交多个任务
        futures = [executor.submit(task_with_random_delay, f"T{i}") for i in range(5)]
    
        # 按完成顺序处理结果
        for future in concurrent.futures.as_completed(futures):
            try:
                result = future.result()
                print(result)
            except Exception as exc:
                print(f"Task generated an exception: {exc}")
    

(二)concurrent.futures.wait(): 批量管理 Future 状态

  • 功能: 同时等待一组 Future 对象达到特定状态。
  • 签名: wait(fs, timeout=None, return_when=ALL_COMPLETED)
  • 参数:
    • fs: Future 对象的集合。
    • timeout: 超时时间(秒)。None 表示无限等待。
    • return_when: 决定何时返回。可选值:
return_when可选值功能描述
FIRST_COMPLETED当任意一个 Future 完成(成功或失败)或被取消时,立即返回。
FIRST_EXCEPTION当任意一个 Future 抛出异常时,立即返回;若所有 Future 均无异常,则等待所有完成后返回(等价于 ALL_COMPLETED)。
ALL_COMPLETED(默认值)当所有 Future 都完成(成功或失败)或被取消时,才返回。
  • 返回值: 一个命名元组 (done, not_done),其中 done 是已完成的 Future 集合,not_done 是尚未完成的 Future 集合。
  • 应用场景:
    • 实现超时控制。
    • 等待至少一个任务成功完成。
    • 批量检查状态。
    import concurrent.futures
    import time
    def slow_task(name, success=True):
        time.sleep(2)
        if success:
            return f"Success: {name}"
        else:
            raise ValueError(f"Failed: {name}")
    
    with concurrent.futures.ThreadPoolExecutor() as executor:
        # 提交一个成功和一个失败的任务
        future1 = executor.submit(slow_task, "FastSuccess", True)
        future2 = executor.submit(slow_task, "SlowFailure", False)
    
        # 等待,直到第一个异常出现
        done, not_done = concurrent.futures.wait(
            [future1, future2],
            return_when=concurrent.futures.FIRST_EXCEPTION
        )
    
        print("Done:", done)
        print("Not Done:", not_done)
    
        # 处理已完成的任务
        for future in done:
            try:
                result = future.result()
                print("Result:", result)
            except Exception as exc:
                print("Exception:", exc)
    

(三)异常处理

  • 关键原则: 任务中抛出的异常不会立即中断主线程。异常被封装在 Future 对象中。
  • 捕获异常:
    • 调用 future.result()future.exception() 时,异常才会被重新抛出或获取。
    • 必须在调用这些方法时使用 try-except 块。
    future = executor.submit(divide, 10, 0)
    try:
        result = future.result()
    except ZeroDivisionError as e:
        print(f"Caught division by zero: {e}")
    
  • map() 的异常处理: executor.map() 返回的迭代器在遍历时,如果某个任务抛出异常,该异常会在遍历到对应位置时由 map 迭代器抛出,并且迭代会停止。可以结合 try-except 在循环中处理。
    for arg, result in zip(args, executor.map(func, args)):
        try:
            print(f"{arg} -> {result}")
        except Exception as e:
            print(f"Error processing {arg}: {e}")
    

(四)资源管理与上下文管理器

  • 强烈推荐使用 with 语句创建执行器。它确保 shutdown() 方法一定会被调用,即使发生异常。
    with concurrent.futures.ThreadPoolExecutor() as executor:
        # ... 提交任务 ...
        pass # shutdown() 自动被调用
    
  • 手动调用 shutdown()
    executor = concurrent.futures.ThreadPoolExecutor()
    try:
        # ... 提交任务 ...
        results = [future.result() for future in futures]
    finally:
        executor.shutdown(wait=True) # 确保等待完成
    

四、 实践、陷阱与替代方案

(一)实际应用模式

1. 批量下载/上传数据
 import concurrent.futures
 import requests
 from pathlib import Path

 def download_file(url, filename):
     response = requests.get(url, stream=True)
     response.raise_for_status()
     with open(filename, 'wb') as f:
         for chunk in response.iter_content(chunk_size=8192):
             f.write(chunk)
     return f"Downloaded {filename}"

 urls_and_files = [
     ('https://example.com/file1.zip', 'file1.zip'),
     ('https://example.com/file2.zip', 'file2.zip'),
     # ...
 ]

 with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
     # 提交所有下载任务
     future_to_url = {
         executor.submit(download_file, url, file): (url, file)
         for url, file in urls_and_files
     }

     for future in concurrent.futures.as_completed(future_to_url):
         url, file = future_to_url[future]
         try:
             result = future.result()
             print(result)
         except Exception as exc:
             print(f"Downloading {url} to {file} generated an exception: {exc}")
2. 预取数据: 提交计算任务后,立即开始处理已经完成的任务,而不是等待所有任务。
 with concurrent.futures.ProcessPoolExecutor() as executor:
     futures = [executor.submit(cpu_intensive_func, data) for data in large_dataset]

     for future in concurrent.futures.as_completed(futures):
         try:
             result = future.result()
             process_result_immediately(result) # 一有结果就处理
         except Exception as e:
             log_error(e)

(二)常见陷阱与解决方案

1. 总结表格
陷阱名称典型错误/场景根本原因解决方案
陷阱1:ProcessPoolExecutor中使用不可 pickle 的对象抛出 AttributeError: Can't pickle local object '...'向进程池提交的任务中包含不可被 pickle 序列化的对象,如嵌套函数、lambda 表达式、带有不可序列化属性的类实例等(进程间通信依赖 pickle 传递数据)。1. 将嵌套函数、lambda 移至模块顶层定义;
2. 使用可 pickle 的数据结构(如基础类型、标准库容器);
3. 重构代码,避免传递不可序列化对象。
陷阱2:忘记处理异常导致程序静默失败程序无报错提示,但任务结果缺失、不完整或错误。未通过 future.result()future.exception() 获取任务状态,导致任务执行中抛出的异常被自动忽略,无法感知错误。1. 调用 future.result() 时必须搭配 try-except 捕获异常;
2. 使用 concurrent.futures.as_completed() 遍历 Future 对象,逐一处理结果与异常;
3. 注册回调函数时,在回调内通过 future.exception() 检查异常。
陷阱3:在 initializer 中引发异常线程池/进程池无法正常启动,或启动后任务执行异常。执行器初始化时(initializer 函数)抛出未捕获的异常,导致工作线程/进程启动失败,进而影响整个执行器。1. 确保 initializer 函数逻辑健壮,避免潜在异常;
2. 在 initializer 内部添加 try-except 捕获异常并做好日志记录;
3. 默认不使用 initializer 时,完全不存在这个陷阱
陷阱4:死锁ThreadPoolExecutor 中,任务A等待任务B的结果,任务B因线程池无空闲线程无法启动,双方陷入无限等待。池内任务通过 future.result() 阻塞等待同池内其他任务,且线程池 max_workers 不足,导致等待链闭环,无可用线程执行依赖任务。1. 避免在池内任务中阻塞等待同池的其他 Future 结果;
2. 若存在任务依赖,将被依赖任务移至主线程执行,或拆分任务逻辑减少耦合;
3. 适当调大 max_workers(需结合系统资源评估)。
陷阱5:Windows 上忘记 if __name__ == '__main__':抛出 RuntimeError: An attempt has been made to start a new process before the current process has finished its bootstrapping phase.Windows 系统创建进程采用 spawn 方式,需重新导入主模块;若未用 if __name__ == '__main__': 包裹执行器代码,会导致模块重复执行,触发进程启动异常。必须将 ProcessPoolExecutor 的创建、任务提交等逻辑,放入 if __name__ == '__main__': 代码块内。
2. 代码示例

以下是针对5个concurrent.futures陷阱的具体示例说明,每个陷阱都包含错误示例和正确示例:

陷阱1:在 ProcessPoolExecutor 中使用不可 pickle 的对象

错误示例(使用嵌套函数导致无法序列化):

import concurrent.futures

def main():
    # 嵌套函数无法被pickle序列化
    def process_data(data):
        return data * 2
    
    with concurrent.futures.ProcessPoolExecutor() as executor:
        # 提交任务时会抛出PickleError
        results = list(executor.map(process_data, [1, 2, 3]))
    print(results)

if __name__ == "__main__":
    main()

正确示例(将函数定义在模块顶层):

import concurrent.futures

# 定义在模块顶层的函数可被正常序列化
def process_data(data):
    return data * 2

def main():
    with concurrent.futures.ProcessPoolExecutor() as executor:
        results = list(executor.map(process_data, [1, 2, 3]))
    print(results)  # 输出: [2, 4, 6]

if __name__ == "__main__":
    main()
陷阱2:忘记处理异常导致程序静默失败

错误示例(未处理任务中抛出的异常):

import concurrent.futures

def risky_task(n):
    if n == 2:
        raise ValueError("数字不能为2")  # 抛出异常
    return n * 2

with concurrent.futures.ThreadPoolExecutor() as executor:
    futures = [executor.submit(risky_task, i) for i in [1, 2, 3]]
    
    # 只获取了部分结果,忽略了异常
    results = []
    for future in futures:
        # 未调用result()或exception(),异常被静默忽略
        if future.done():
            results.append(future.result())
    
    print(results)  # 可能输出不完整的结果,且无错误提示

正确示例(显式处理异常):

import concurrent.futures

def risky_task(n):
    if n == 2:
        raise ValueError("数字不能为2")
    return n * 2

with concurrent.futures.ThreadPoolExecutor() as executor:
    futures = [executor.submit(risky_task, i) for i in [1, 2, 3]]
    
    results = []
    for future in concurrent.futures.as_completed(futures):
        try:
            result = future.result()  # 触发异常
            results.append(result)
        except ValueError as e:
            print(f"捕获异常: {e}")  # 显式处理异常
    
    print("成功结果:", results)  # 输出: 成功结果: [2, 6]
陷阱3:在 initializer 中引发异常

错误示例(initializer函数抛出未处理异常):

import concurrent.futures

def bad_initializer():
    # 初始化函数中抛出未处理异常
    raise RuntimeError("初始化失败!")

# 执行器无法正常启动
with concurrent.futures.ThreadPoolExecutor(
    initializer=bad_initializer
) as executor:
    try:
        # 提交任务会失败
        future = executor.submit(lambda x: x*2, 10)
        print(future.result())
    except Exception as e:
        print(f"执行器启动失败: {e}")  # 输出初始化异常

正确示例(在initializer中处理异常):

import concurrent.futures
import logging

def safe_initializer():
    try:
        # 可能失败的初始化操作
        logging.basicConfig(level=logging.INFO)
        logging.info("初始化成功")
    except Exception as e:
        # 捕获并记录异常,避免执行器崩溃
        logging.error(f"初始化警告: {e}")

with concurrent.futures.ThreadPoolExecutor(
    initializer=safe_initializer
) as executor:
    future = executor.submit(lambda x: x*2, 10)
    print(future.result())  # 输出: 20 (即使初始化有警告也能正常执行)

默认不使用 initializer 时,完全不存在这个陷阱,因为没有初始化逻辑可引发异常。

陷阱4:死锁

错误示例(线程池内任务相互等待):

import concurrent.futures

def task1(executor):
    # 任务1等待任务2的结果,但任务2需要线程池空闲才能执行
    future = executor.submit(task2, 10)
    return future.result()  # 死锁点: 等待任务2完成,但无空闲线程执行任务2

def task2(n):
    return n * 2

# 线程池大小为1,导致死锁
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
    try:
        # 提交任务1后,线程池已无空闲线程
        result = executor.submit(task1, executor).result()
        print(result)
    except Exception as e:
        print(f"发生死锁: {e}")

正确示例(避免池内任务相互依赖):

import concurrent.futures

def task1(value):
    # 直接使用预计算的值,不依赖其他池内任务
    return value * 2

def task2(n):
    return n * 2

# 方案1: 增大线程池容量
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
    # 任务1和任务2可并行执行
    future2 = executor.submit(task2, 10)
    future1 = executor.submit(task1, future2.result())
    print(future1.result())  # 输出: 40

# 方案2: 拆分依赖关系,在主线程处理依赖
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
    future2 = executor.submit(task2, 10)
    value2 = future2.result()  # 主线程等待任务2完成
    future1 = executor.submit(task1, value2)  # 再提交任务1
    print(future1.result())  # 输出: 40
陷阱5:在 Windows 上忘记 if __name__ == '__main__':

错误示例(Windows环境下无主模块保护):

# 在Windows系统中运行会报错
import concurrent.futures

def process_data(n):
    return n * 2

# 直接在全局作用域使用ProcessPoolExecutor
with concurrent.futures.ProcessPoolExecutor() as executor:
    results = list(executor.map(process_data, [1, 2, 3]))
print(results)

# 错误: RuntimeError: An attempt has been made to start a new process...

正确示例(添加主模块保护):

# 在Windows系统中正常运行
import concurrent.futures

def process_data(n):
    return n * 2

# 关键: 使用if __name__ == '__main__'包裹进程池代码
if __name__ == "__main__":
    with concurrent.futures.ProcessPoolExecutor() as executor:
        results = list(executor.map(process_data, [1, 2, 3]))
    print(results)  # 输出: [2, 4, 6]

这些示例清晰展示了每个陷阱的触发场景和对应的解决方案,实际开发中需根据具体场景选择合适的处理方式,尤其注意跨平台兼容性和异常处理。

(三)concurrent.futures vs asyncio

特性concurrent.futures (线程/进程)asyncio
模型多线程/多进程 (抢占式)单线程事件循环 (协作式)
GIL线程受限制,进程不受限不受限制 (I/O 等待时让出)
适用I/O 密集 (线程), CPU 密集 (进程)I/O 密集
复杂度相对简单,同步风格 API较高,需理解 async/await
阻塞result() 会阻塞线程await 挂起协程,不阻塞事件循环
数据共享线程易,进程难易 (单线程)
库支持广泛需要 async 兼容库 (aiohttp, asyncpg)
  • 选择建议:
    • 简单的 I/O 并发或 CPU 并行:concurrent.futures
    • 高吞吐量的 I/O 服务(如 Web 服务器、大量并发连接):asyncio
    • 混合场景:可以结合使用(例如,用 asyncio 管理网络,用 ProcessPoolExecutor 处理 CPU 任务)。

(四)更高级的替代方案

  • multiprocessing.Pool: ProcessPoolExecutor 的前身,功能类似,但 API 不同。concurrent.futures 是更现代的推荐选择。
  • joblib: 专为科学计算设计,对 numpy 数组优化好,语法更简洁(Paralleldelayed)。
  • celery: 分布式任务队列,适用于跨机器、持久化、复杂工作流的场景。

总结

  1. 理解并发与并行的区别
    • 并发:同时处理多个任务(交替执行)
    • 并行:同时执行多个任务(真正同时)
  2. 选择合适的执行器
    • I/O 密集型 → ThreadPoolExecutor
    • CPU 密集型 → ProcessPoolExecutor
  3. 掌握核心 API
    • submit():提交单个任务
    • map():提交批量任务
    • as_completed():按完成顺序处理结果
    • wait():批量管理任务状态
  4. 正确处理异常
    • result()exception() 调用时捕获异常
    • 使用 as_completed 时在循环中处理异常
  5. 注意平台差异
    • Windows 上使用 ProcessPoolExecutor 时需在 if __name__ == '__main__':
    • 确保传递的参数和返回值可序列化
  6. 性能调优
    • 为 I/O 密集型任务设置较大的线程池
    • 为 CPU 密集型任务设置与 CPU 核心数匹配的进程池
  7. 资源管理
    • 始终使用 with 语句管理执行器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值