Python进程第三方包:from concurrent.futures import ProcessPoolExecutor,multiprocessing.pool, multiprocessing.process
不推荐第三方封装的进程池,直接使用进程Process
1. Python进程池
目前主要使用的进程池第三方包为from concurrent.futures import ProcessPoolExecutor,multiprocessing.pool;
ProcessPoolExecutor的使用:
from concurrent.futures import ProcessPoolExecutor
def task_func(x):
return x * x
if __name__ == "__main__":
with ProcessPoolExecutor() as executor:
# 提交任务到进程池
result = executor.submit(task_func, 5) # 提交一个任务,并传递参数
# 获取任务的执行结果
print(result.result()) # 阻塞获取任务结果
# 批量提交任务到进程池
inputs = [1, 2, 3, 4, 5]
results = [executor.submit(task_func, x) for x in inputs]
# 获取批量任务的执行结果
for future in results:
print(future.result()) # 阻塞获取任务结果
multiprocessing.pool的使用:
import multiprocessing
# 定义一个需要并行处理的函数
def process_func(x):
return x * x
if __name__ == '__main__':
# 创建进程池,指定进程数
pool = multiprocessing.Pool(processes=4)
# 定义输入数据
inputs = [1, 2, 3, 4, 5]
# 使用进程池并行执行process_func函数
results = pool.map(process_func, inputs)
# 输出结果
print(results)
# 关闭进程池,并释放资源
pool.close()
pool.join()
需要注意的点:
对于进程池中的子进程,它们并不继承父进程的内存空间和上下文。进程池中的子进程是在父进程启动时预先创建的,这些子进程是独立的,没有直接继承父进程的内存空间和上下文。
在进程池中,当需要执行任务时,父进程会将任务函数和参数进行序列化,并通过操作系统提供的机制(如管道或共享内存)将序列化后的数据传递给子进程。子进程接收到数据后,会进行反序列化,并在自己的内存空间中执行任务函数。
由于进程池中的子进程是预先创建的,它们会一直保持在内存中,以便在需要时可以立即执行任务。这避免了为每个任务创建和销毁进程的开销,提高了任务的执行效率。
需要注意的是,尽管进程池中的子进程不继承父进程的内存空间和上下文,但它们可以通过序列化和反序列化的方式,从父进程获取任务函数和参数的定义。子进程在执行任务时,会重新创建函数对象,并在自己的内存空间中执行。这样可以确保每个子进程都独立执行任务,互不影响。
顶级函数与非顶级函数的理解:
在使用multiprocessing.Pool时,被调用的函数不能是局部函数(local function)或嵌套函数(nested function)所导致的。
pickle模块是用于Python对象的序列化和反序列化的工具。在使用multiprocessing.Pool时,它需要将被调用的函数及其参数序列化并在不同的进程之间传递。而局部函数或嵌套函数无法被序列化,因此会导致AttributeError: Can't pickle local object的错误。
要解决这个问题,可以将被调用的函数定义为独立的顶级函数,而不是局部函数或嵌套函数。确保被调用的函数在Pool使用的上下文之外。
Python函数对象的序列化可参考(需要深刻理解):https://www.cnblogs.com/chenhuabin/p/10502096.html
2. Python进程
multiprocessing.process的使用:
import multiprocessing
def worker(num):
"""子进程要执行的任务"""
print(f"Worker {num} started")
if __name__ == '__main__':
# 创建子进程并启动
p1 = multiprocessing.Process(target=worker, args=(1,))
p2 = multiprocessing.Process(target=worker, args=(2,))
p1.start()
p2.start()
# 等待子进程执行结束
p1.join()
p2.join()
需要注意的点:
对于multiprocessing.Process来说,任务函数不需要是顶级函数,可以是任意可调用的函数,包括局部函数或嵌套函数concurrent.f.ProcessPoolExecutor的要求,multiprocessing并不需要将任务函数序列化传递给子进程。
主进程-》子进程不需要序列化,直接fork即可;
个人理解,进程池中的进程事先规定需要进程通信,因此默认序列化任务函数;process只存在父进程向子进程的内存复制;
3. 一个例子
from concurrent.futures import (ThreadPoolExecutor, as_completed)
from concurrent.futures import ProcessPoolExecutor
import concurrent.futures
import math
import time
import traceback
# import sys
# sys.path.append("../")
import threading
# ParallelTool
class ParallelProcessTool():
@staticmethod
def run(fun, param_arr: list[tuple], parallel_num:int = 10):
task_num = len(param_arr)
# 多进程
res_list, fail_num = [None]*task_num, 0
grp_num = math.ceil(task_num / parallel_num)
if task_num == 0:
return [], 0
def impl_fun(param, idx):
try:
return idx, fun(*param)
except Exception as e:
return idx, None
def process_func_test(index):
print(f"process_func_test = {index}")
def thread_func_test(index):
print(f"thread_func_test = {index}")
def process_func(param_list):
# 多线程处理
with ThreadPoolExecutor(max_workers=len(param_list)) as executor:
task = []
for param in param_list:
task.append(executor.submit(thread_func_test, param, param[0]))
# 按位置处理结果
for future in as_completed(task):
idx, res = future.result()
res_list[idx] = res
# 首先多进程处理
with ProcessPoolExecutor(max_workers = parallel_num) as executor:
all_task = []
for i in range(parallel_num):
# 多线程参数列表
param_list = param_arr[i*grp_num:(i+1)*grp_num]
all_task.append(executor.submit(process_func_test, param_list))
# 等待所有进程执行完毕
concurrent.futures.wait(all_task)
return res_list, fail_num
if __name__ == "__main__":
def run_impl(idx):
print(f"idx:{idx} start time:{time.time()}")
time.sleep(4)
print(f"idx:{idx} end time:{time.time()}")
params = [(i,) for i in range(10)]
res_list = ParallelProcessTool.run("", run_impl, params, 2)
自己调试,体验一下进程池与进程的不同