用concurrent 库并行:ThreadPoolExecutor与ProcessPoolExecutor及as_completed

通常当我们谈论 Python并发编程时,会提到 concurrent 库的concurrent.futures 模块,这个模块提供了一组高级的接口,用于异步执行函数(也称为任务)。它允许你轻松地并行执行任务,而不需要直接处理线程或进程的低级细节。

concurrent.futures 提供了两个主要的类:

  1. ThreadPoolExecutor:用于在单独的线程中异步执行函数。
  2. ProcessPoolExecutor:用于在单独的进程中异步执行函数,这对于 CPU 密集型任务特别有用,因为它可以利用多核处理器。

使用示例

下面是一些使用 concurrent.futures 的示例,展示了如何异步执行函数并获取结果。

示例 1:使用 ThreadPoolExecutor
import concurrent.futures  
import time  
  
def task(n):  
    print(f"Task {n} is starting.")  
    time.sleep(2)  # 模拟 I/O 密集型任务  
    return f"Task {n} result"  
  
# 创建一个 ThreadPoolExecutor,最大线程数为 3  
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:  
    # 使用 submit 方法提交任务  
    futures = [executor.submit(task, i) for i in range(5)]  
      
    # 使用 as_completed 方法迭代完成的任务  
    for future in concurrent.futures.as_completed(futures):  
        try:  
            result = future.result()  # 获取任务结果  
            print(result)  
        except Exception as exc:  
            print(f"{future} generated an exception: {exc}")

在这个例子中,我们创建了一个 ThreadPoolExecutor,最大线程数为 3。然后我们提交了 5 个任务,这些任务会并发执行(但受限于线程池的大小,最多只有 3 个任务会同时运行)。

示例 2:使用 ProcessPoolExecutor
import concurrent.futures  
import os  
  
def task(n):  
    print(f"Task {n} is running in process {os.getpid()}.")  
    return n * n  # 模拟 CPU 密集型任务  
  
# 创建一个 ProcessPoolExecutor,默认使用 os.cpu_count() 作为进程数  
with concurrent.futures.ProcessPoolExecutor() as executor:  
    # 使用 map 方法提交任务,类似于内置的 map 函数  
    results = list(executor.map(task, range(10)))  
      
    print(results)

在这个例子中,我们使用了 ProcessPoolExecutor,它会在不同的进程中并发执行任务。这通常用于 CPU 密集型任务,因为 Python 的全局解释器锁(GIL)会限制多线程在 CPU 密集型任务中的性能。

分析

  • ThreadPoolExecutor 适用于 I/O 密集型任务,如文件操作、网络请求等。
  • ProcessPoolExecutor 适用于 CPU 密集型任务,因为它可以绕过 Python 的全局解释器锁。
  • 提交任务后,可以使用 future.result() 获取任务的结果,这将会阻塞调用线程直到任务完成。
  • 使用 as_completed 方法可以迭代已完成的任务,而不需要等待所有任务都完成。

通过 concurrent.futures 模块,你可以更方便地进行并发编程,而不需要直接处理复杂的线程和进程管理。

示例 3:使用 as_completed 函数

as_completed 函数是一个非常有用的工具,它允许你迭代一组 Future 对象,这些对象代表了异步执行的任务。重要的是,as_completed 会按照任务完成的顺序返回这些 Future 对象,而不是按照你提交它们的顺序。

import concurrent.futures  
import time  
  
# 定义一个简单的任务函数  
def task(n):  
    time.sleep(n)  # 模拟任务执行时间  
    return f"Task {n} completed"  
  
# 创建一个 ThreadPoolExecutor 实例  
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:  
    # 提交一系列任务  
    futures = [executor.submit(task, i) for i in range(1, 5)]  
      
    # 使用 as_completed 迭代完成的任务  
    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}")

在这个例子中,我们创建了一个 ThreadPoolExecutor 实例,并提交了 4 个任务,每个任务都会休眠一段时间(从 1 秒到 4 秒不等)。由于我们设置了最大工作线程数为 3,因此最多会有 3 个任务同时运行。

然而,由于 as_completed 会按照任务完成的顺序返回 Future 对象,因此我们会看到输出结果的顺序与提交任务的顺序不同。具体来说,首先完成的任务(即休眠时间最短的任务)将首先被打印出来

示例 4:并行处理大量数据
import concurrent.futures  
import argparse  
import time  # 用于模拟处理时间  
  
# 假设的数据集,实际应用中应从文件或数据库中加载  
dataset = {  
    "validation": [  
        {"id": 1, "data": "some validation data 1"},  
        {"id": 2, "data": "some validation data 2"},  
        # ... 更多数据项  
        {"id": 10, "data": "some validation data 10"}  
    ]  
}  
  
# 模拟数据处理函数  
def process_data(data):  
    print(f"Processing data item with id: {data['id']}")  
    time.sleep(2)  # 模拟处理时间  
    print(f"Finished processing data item with id: {data['id']}")  
    return f"Processed {data['data']}"  
  
# 解析命令行参数  
def parse_arguments():  
    parser = argparse.ArgumentParser(description="Process validation data in parallel.")  
    parser.add_argument("--max_workers", type=int, default=4, help="Maximum number of worker threads.")  
    return parser.parse_args()  
  
def main():  
    args = parse_arguments()  
      
    # 使用ThreadPoolExecutor来并行处理数据  
    with concurrent.futures.ThreadPoolExecutor(max_workers=args.max_workers) as executor:  
        # 提交所有验证数据项给executor进行处理  
        futures = [executor.submit(process_data, data) for data in dataset["validation"]]  
          
        # 遍历并等待所有future完成,同时获取处理结果(如果有的话)  
        for future in concurrent.futures.as_completed(futures):  
            try:  
                result = future.result()  # 获取处理结果  
                print(f"Result: {result}")  
            except Exception as e:  
                print(f"An error occurred: {e}")  
  
if __name__ == "__main__":  
    main()

代码解释

  1. 导入必要的模块
    • concurrent.futures:用于并行执行任务。
    • argparse:用于解析命令行参数。
    • time:用于模拟数据处理所需的时间(在实际应用中,这里会是真正的数据处理逻辑)。
  2. 定义数据集
    • dataset:一个包含验证数据的字典。在实际应用中,这些数据可能来自文件、数据库或其他数据源。
  3. 定义数据处理函数
    • process_data(data):一个模拟的数据处理函数,它接受一个数据项并返回处理后的结果。在这个例子中,它只是打印处理开始和结束的消息,并模拟了一个处理时间。
  4. 解析命令行参数
    • parse_arguments():一个函数,用于解析命令行参数。这里我们定义了一个--max_workers参数,用于指定最大工作线程数。
  5. 主函数
    • main():应用程序的入口点。
      • 解析命令行参数。
      • 使用ThreadPoolExecutor创建一个线程池,并指定最大工作线程数。
      • 提交所有验证数据项给线程池进行处理,并获取一个future对象列表。
      • 遍历future对象列表,并等待每个future完成。使用future.result()获取处理结果(如果有的话),并处理可能出现的异常。注:as_completed 的主要作用是提供一个迭代器,该迭代器会在任务完成时立即产生对应的 Future 对象。这样,你就可以在任务完成后立即处理其结果,而不需要等待所有任务都完成。
  6. 程序入口
    • if __name__ == "__main__"::确保当脚本被直接运行时,main()函数会被调用。

运行程序

你可以通过命令行运行这个程序,并指定--max_workers参数来控制并行处理的工作线程数。例如:

python script_name.py --max_workers=5

这将启动一个包含5个工作线程的线程池,用于并行处理验证数据集中的数据项。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值