Python进程池与进程

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)

自己调试,体验一下进程池与进程的不同 

  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值