python多进程(类成员函数、log在多进程中使用、自己维护进程池)

本文探讨了Python multiprocessing Pool中子进程挂起导致主进程无法终止的问题,并介绍了两种解决方案:使用Pool的apply_async避免阻塞和自定义进程池加入进程状态检查。通过实例演示了如何调用类方法实现多进程并确保正确退出。
部署运行你感兴趣的模型镜像

写这篇的原因参考之前写的一篇博客Python 多进程池中子进程挂了导致进程不能结束的问题小结_文锦渡的博客-优快云博客_python 子进程不能退出问题:Python multiprocessing pool not shutting down while child processes is oom开篇点题:在使用python的multiprocessing中的pool时,会出现子进程因为各种原因挂了之后主进程任务停不下来的情况。这是因为主进程没能发现子进程挂了而判定结束,从而卡在了这个地方,是python的一个bug。解决办法有两个,且看下文分解~https://blog.youkuaiyun.com/mxdsdo09/article/details/119728373?spm=1001.2014.3001.5501

 主要是为了记录自己实现的

1、python进程池维护

2、logging在多进程中的使用

3、调用类的成员函数实现多进程

一、调用类的成员函数实现多进程

下面这部分代码是调用python自带的multiprocessing.Pool的进程池实现的对类的成员函数的调用,优点是方便快捷,但缺点在开头的另一篇文章里提到了 无法判断OOM和子进程GG 主进程会一直运行 不能中断

import os
import sys
import time
import multiprocessing


class PrintNumber():

    def __init__(self, multi_num):
        self.multi_num = multi_num

    def run_task(self):
        sum_num = 1
        for i in range(5):
            sum_num += i
            time.sleep(1)
            print("multi_num = {}, output = {}".format(self.multi_num, sum_num))


if __name__ == "__main__":
    print("multiprocess test")

    process_pool = multiprocessing.Pool(3)
    for i in range(3):
        test = PrintNumber(i)
        process_pool.apply_async(test.run_task, args=())

    print("main thread")
    test_main = PrintNumber(10)
    test_main.run_task()

    process_pool.close()
    process_pool.join()

    print("thread join success!")

注意!process_pool.apply_async(test.run_task, args=())不要在test.run_task后加括号(test.run_task()),否则不会报错且输出相当于一个进程。。。原因懂得得大佬可以解释一下~

输出

#python3 test.py 
multiprocess test
main thread
multi_num = 2, output = 1
multi_num = 1, output = 1
multi_num = 10, output = 1
multi_num = 0, output = 1
multi_num = 2, output = 2
multi_num = 0, output = 2
multi_num = 1, output = 2
multi_num = 10, output = 2
multi_num = 2, output = 4
multi_num = 1, output = 4
multi_num = 0, output = 4
multi_num = 10, output = 4
multi_num = 1, output = 7
multi_num = 2, output = 7
multi_num = 0, output = 7
multi_num = 10, output = 7
multi_num = 1, output = 11
multi_num = 2, output = 11
multi_num = 0, output = 11
multi_num = 10, output = 11
thread join success!

这里做了个验证,得for循环给PrintNumber实例化同一个名称test不会导致进程运行异常(原因暂时不清楚,感觉后面给test赋值应该会覆盖前面先实例化的呀?)

二、自己构建进程池实现

加入对进程数量和结果的判断,防止子进程挂了没能发现

import os
import sys
import time
import multiprocessing
import logging

# 全局变量在多进程中使用
global g_log
g_log = None


def init_log(log_file):
    log = logging.getLogger()  # 不加名称设置root logger
    log.setLevel(logging.DEBUG)
    formatter = logging.Formatter("%(asctime)s<%(levelname)s>: %(message)s",
                                  datefmt="%Y-%m-%d %H:%M:%S")

    # 使用FileHandler输出到文件
    fh = logging.FileHandler(log_file)
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(formatter)

    # 使用StreamHandler输出到屏幕
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    ch.setFormatter(formatter)

    # 添加两个Handler
    log.addHandler(ch)
    log.addHandler(fh)
    log.info("init log success!")

    return log


class PrintNumber():

    def __init__(self, multi_num):
        self.multi_num = multi_num

    def run_task(self, thread_q):
        thread_id = os.getpid()
        result = []

        # debug 验证失败的情况
        # if not self.multi_num > 0:
        #     success = False
        #     thread_q.put((result, success))
        #     return

        sum_num = 1
        for i in range(5):
            sum_num += i
            time.sleep(1)
            g_log.info("thread_id<{}> multi_num = {}, output = {}".format(
                thread_id, self.multi_num, sum_num))

        success = True
        result.append(sum_num)
        thread_q.put((result, success))


if __name__ == "__main__":
    log_file = os.path.join(os.path.abspath(os.path.dirname(__file__)),
                            "test.log")
    log = init_log(log_file)

    log.info("multiprocess test")
    g_log = log

    thread_list = []
    thread_q = multiprocessing.Queue()

    for i in range(3):
        test = PrintNumber(i)
        # 重点注意!
        p = multiprocessing.Process(target=test.run_task, args=(thread_q,))
        p.start()
        thread_list.append(p)

    log.info("main thread")
    test_main = PrintNumber(10)
    test_main.run_task(thread_q)

    for p in thread_list:
        p.join()
    log.info("thread join success!")

    if not len(thread_list) + 1 == thread_q.qsize():
        log.error(
            "thread process false! input thread num:{}, output thread num:{}".
            format(len(thread_list) + 1, thread_q.qsize()))
        sys.exit(-1)

    result_list = []
    sucess_list = []
    for i in range(thread_q.qsize()):
        thread_q_temp = thread_q.get()
        result_list.extend(thread_q_temp[0])
        sucess_list.append(thread_q_temp[1])

    for sucess in sucess_list:
        if not sucess:
            log.error("thread process false! sucess is false!")
            sys.exit(-1)

    log.info("all thread process success!")

参考:

Python logging同时输出到屏幕和文件 - 孤鸿的天空

Python logging浅尝(将log同时输出到Console和日志文件)_小桔帽的博客-优快云博客_python日志输出到文件

您可能感兴趣的与本文相关的镜像

Python3.11

Python3.11

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

<think>好的,我现在要处理用户在SGE环境中运行Python多进程任务时的注意事项的问题。首先,我需要理解用户的需求。他们可能正在使用Sun Grid Engine(SGE)这样的集群管理系统,想要利用Python多进程模块来并行化任务,但不确定需要注意哪些问题。接下来,我需要回顾相关知识,包括SGE的工作机制、Python多进程的特点以及两者结合时可能遇到的挑战。 首先,SGE是一个作业调度系统,负责分配计算资源。用户提交作业时,需要指定资源需求,如CPU、内存等。而Python的multiprocessing模块可以创建多个进程来并行执行任务,这可能会与SGE的资源管理产生交互。需要考虑的关键点包括资源请求的准确性、进程数与SGE分配的CPU核心的匹配、内存管理、环境变量设置、错误处理、日志记录以及可能的替代方案。 接下来,资源请求方面,用户需要确保在SGE脚本中使用`-pe`参数指定正确的并行环境,比如`-pe smp 8`来请求8个核心。如果Python代码中启动的进程数超过分配的核心数,可能会导致资源竞争,影响性能甚至导致任务失败。因此,用户应该动态获取SGE分配的核心数,可以通过环境变量`NSLOTS`来获取,并在代码中设置进程池的大小。 内存管理也是一个重要问题。每个子进程都会占用内存,如果总内存超过SGE分配的限制,作业可能会被杀死。用户需要估算每个进程的内存需求,并在提交作业时使用`-l h_vmem`参数预留足够的内存。同时,避免内存泄漏,确保及时释放不再使用的资源。 环境变量和路径问题需要注意,因为SGE作业可能在不同的节点上运行,环境可能与登录节点不同。用户需要确保所有依赖的Python库在计算节点上可用,可以通过使用虚拟环境或在作业脚本中设置`PYTHONPATH`。此外,使用绝对路径来访问文件,避免相对路径导致的错误。 错误处理和日志记录方面,多进程任务中的异常可能难以追踪,建议每个子进程将错误信息写入日志文件,并捕获异常。使用`try-except`块包裹任务代码,并将错误信息记录到文件中,便于后续排查。同时,确保日志文件不会同时被多个进程写入,避免冲突。 任务划分与数据传递方面,Python的multiprocessing模块在进程间通信时可能会有较大的开销,特别是在跨节点的情况下。如果任务需要大量数据传输,考虑使用共享内存或避免频繁的数据交换。例如,使用`multiprocessing.Array`或`multiprocessing.Value`来共享数据,或者将任务设计为尽可能独立,减少通信需求。 此外,需要考虑SGE的并行环境是否支持多节点任务。如果任务需要跨节点运行,Python的multiprocessing可能无法直接利用多节点资源,这时候可能需要使用其他库,如Dask或MPI4Py,来跨节点协调任务。例如,Dask可以部署在SGE集群上,动态分配任务到多个节点,而用户提到的引用[1]中提到Dask的较低任务开销和良好的Python集成可能是一个优势。 替代方案部分,用户可能需要了解除了multiprocessing之外的其他并行处理工具,比如Dask或PySpark。特别是如果任务需要处理大数据或跨节点运行,Dask可能更适合,因为它可以与SGE集成,动态管理资源,如引用[1]所述,Dask相比PySpark有更低的任务开销和更好的Python集成。 测试和调试方面,建议先在本地小规模测试代码,确认无误后再提交到SGE。使用较小的数据集和进程数进行测试,确保资源请求和进程管理正确。调试时可以利用引用[2]中提到的一般技术,比如简化问题、使用日志记录等。 最后,注意事项可能包括避免在任务中直接使用CPU绑定操作,特别是当进程数超过物理核心时导致的争用;确保所有子进程在作业结束前正确终止,避免僵尸进程;以及处理信号,使得SGE能够正确终止作业。 总结以上思考,用户需要关注资源分配、进程管理、内存控制、环境配置、错误处理、任务设计、替代方案选择等方面。在回答时,需要结构清晰,分点说明,并引用相关的资料支持建议,如Dask的优势和调试技术。</think>在SGE(Sun Grid Engine)环境中运行Python多进程任务时,需注意以下关键事项: --- ### 1. **资源请求与分配** - **明确指定并行环境**:在提交作业时,通过SGE的`-pe`参数指定并行环境(如`-pe smp 8`表示请求8个CPU核心)[^3]。 - **匹配进程数与CPU核心**:在Python代码中动态获取SGE分配的核心数,避免超用: ```python import os n_cores = int(os.environ.get("NSLOTS", 1)) with Pool(n_cores) as pool: pool.map(task, data) ``` - **内存预留**:通过`-l h_vmem`参数为每个进程预留内存(如`-l h_vmem=2G`),避免因内存溢出导致任务被终止。 --- ### 2. **环境与路径配置** - **依赖一致性**:确保计算节点与登录节点的Python环境一致,可通过以下方法: - 使用虚拟环境并在作业脚本中激活。 - 在脚本中设置`PYTHONPATH`环境变量。 - **绝对路径优先**:所有文件操作使用绝对路径,避免因工作目录不同导致的路径错误。 --- ### 3. **进程间通信优化** - **减少数据传输**:多进程任务应尽量减少进程间通信(IPC),优先使用共享内存(如`multiprocessing.Array`)或独立任务设计。 - **避免跨节点通信**:若SGE分配了多节点资源,Python的`multiprocessing`可能无法直接利用,需改用Dask或MPI4Py等跨节点并行库[^1]。 --- ### 4. **错误处理与日志记录** - **异常捕获**:在每个子进程中捕获异常并记录: ```python def task_wrapper(args): try: return process(args) except Exception as e: with open("error.log", "a") as f: f.write(f"Error: {e}\n") raise ``` - **独立日志文件**:为每个进程或任务生成独立日志文件,避免并发写入冲突。 --- ### 5. **替代方案与工具** - **Dask集群部署**:若需跨节点扩展,可部署Dask集群到SGE环境,利用其动态资源管理能力(引用[1]提到Dask的低任务开销优势): ```python from dask_jobqueue import SGECluster cluster = SGECluster(cores=8, memory="16GB") cluster.scale(4) # 启动4个工作节点 ``` - **避免JVM工具的开销**:如PySpark虽支持优化器但需JVM交互,可能不适合轻量级任务[^1]。 --- ### 6. **测试与调试建议** - **本地小规模测试**:先本地测试多进程逻辑,确保无死锁或资源泄露。 - **简化问题定位**:如引用[2]建议,通过逐步增加进程数或数据量定位性能瓶颈。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

文锦渡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值