通常当我们谈论 Python并发编程时,会提到 concurrent
库的concurrent.futures
模块,这个模块提供了一组高级的接口,用于异步执行函数(也称为任务)。它允许你轻松地并行执行任务,而不需要直接处理线程或进程的低级细节。
concurrent.futures
提供了两个主要的类:
ThreadPoolExecutor
:用于在单独的线程中异步执行函数。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()
代码解释
- 导入必要的模块:
concurrent.futures
:用于并行执行任务。argparse
:用于解析命令行参数。time
:用于模拟数据处理所需的时间(在实际应用中,这里会是真正的数据处理逻辑)。
- 定义数据集:
dataset
:一个包含验证数据的字典。在实际应用中,这些数据可能来自文件、数据库或其他数据源。
- 定义数据处理函数:
process_data(data)
:一个模拟的数据处理函数,它接受一个数据项并返回处理后的结果。在这个例子中,它只是打印处理开始和结束的消息,并模拟了一个处理时间。
- 解析命令行参数:
parse_arguments()
:一个函数,用于解析命令行参数。这里我们定义了一个--max_workers
参数,用于指定最大工作线程数。
- 主函数:
main()
:应用程序的入口点。- 解析命令行参数。
- 使用
ThreadPoolExecutor
创建一个线程池,并指定最大工作线程数。 - 提交所有验证数据项给线程池进行处理,并获取一个
future
对象列表。 - 遍历
future
对象列表,并等待每个future
完成。使用future.result()
获取处理结果(如果有的话),并处理可能出现的异常。注:as_completed 的主要作用是提供一个迭代器,该迭代器会在任务完成时立即产生对应的 Future 对象。这样,你就可以在任务完成后立即处理其结果,而不需要等待所有任务都完成。
- 程序入口:
if __name__ == "__main__":
:确保当脚本被直接运行时,main()
函数会被调用。
运行程序
你可以通过命令行运行这个程序,并指定--max_workers
参数来控制并行处理的工作线程数。例如:
python script_name.py --max_workers=5
这将启动一个包含5个工作线程的线程池,用于并行处理验证数据集中的数据项。