Python multiprocessing 打印报错信息和解决进程不运行的问题

712 阅读3分钟

Python multiprocessing 打印报错信息和解决进程不运行的问题

multiprocessing 的使用

multiprocessing的基本用法如下

from multiprocessing import Pool

# 你自定义的需要反复调用的用于并行计算的进程
def your_func(your_data):
    do_something()



if __name__ == '__main__':
    # 定义数量为20的进程池
    process_pool = Pool(20)

    # 从你的数据list中取出数据,作为你的自定义函数的参数,加入进程池
    for your_data in your_list:
        process_pool.apply_async(your_func, (your_data,)) 

    process_pool.close()
    process_pool.join()

需要注意的是进程创建部分的代码必须放在__main__ 函数中,不然,新建的函数会拷贝加载程序文件,除了__main__之外的全局的代码片段会被执行一遍,就导致了子进程也会创建进程池,也会创建子进程,就陷入了无限的子进程创建的循环中,耗尽系统的资源

但是,我在我自己项目中使用该方法的时候,发现your_func并没有执行,控制台也没报错

打印报错信息

  1. 可以使用如下的方式,增加 print_error 回调函数 打印报错信息
from multiprocessing import Pool

# 你自定义的error_callback函数
def print_error(value):
    print("error: ", value)

# 你自定义的需要反复调用的用于并行计算的进程
def your_func(your_data):
    do_something()

if __name__ == '__main__':
    # 定义数量为20的进程池
    process_pool = Pool(20)

    # 从你的数据list中取出数据,作为你的自定义函数的参数,加入进程池
    for your_data in your_list:
        process_pool.apply_async(your_func, (your_data,), error_callback=print_error) 
        # 如上,设置error_callback=自定义的打印错误的函数!!!

    process_pool.close()
    process_pool.join()

上面这种方式只能打印错误信息,不能打印错误堆栈信息。代码如果过长的话,很难定位到出错的代码片段。 2. 可以使用traceback 打印堆栈信息

from multiprocessing import Pool
import multiprocessing

import traceback

def error(msg, *args):
    return multiprocessing.get_logger().error(msg, *args)


class LogExceptions(object):
    def __init__(self, callable):
        self.__callable = callable
        return

    def __call__(self, *args, **kwargs):
        try:
            result = self.__callable(*args, **kwargs)

        except Exception as e:
            # Here we add some debugging help. If multiprocessing's
            # debugging is on, it will arrange to log the traceback
            error(traceback.format_exc())
            # Re-raise the original exception so the Pool worker can
            # clean up
            raise

        # It was fine, give a normal answer
        return result
    pass

if __name__ == '__main__':
    multiprocessing.log_to_stderr() # 加上此行 
    process_pool = Pool(20)

    # 你自定义的需要反复调用的用于并行计算的进程
    def your_func(your_data):
        do_something()

    # 从你的数据list中取出数据,作为你的自定义函数的参数,加入进程池
    for your_data in your_list:
        process_pool.apply_async(LogExceptions(your_func), (your_data,)) # 套上LogExceptions

    process_pool.close() 
    process_pool.join()

进程不运行的问题

我代码的问题

from multiprocessing import Pool

# 你自定义的需要反复调用的用于并行计算的进程
class MyClass:
    def __init__(self, sqliteSql):
        self.sqliteSql = sqliteSql
    def run(self, data):
        # do some thing
        pass

if __name__ == '__main__':
    # 定义数量为20的进程池
    process_pool = Pool(20)
    sqliteSql = SqliteSql() # 我自己封装的一个基于sqlalchemy的对sqlite3访问的类,维护了数据库访问的session等信息。
    myObj = MyClass(sqliteSql)

    # 从你的数据list中取出数据,作为你的自定义函数的参数,加入进程池
    for your_data in your_list:
        process_pool.apply_async(myObj.run, (your_data,), error_callback=print_error) 

    process_pool.close()
    process_pool.join()

上述代码报错

error:  Can't pickle local object 'create_engine.<locals>.connect'

我猜测是因为myObject对象的成员 sqliteSql中包含一些无法使用pickle序列化的对象。无法使用pickle序列化,在创建新的进程的时候,就无法对内存中的内容进行拷贝,导致进程创建失败。

解决方法。我的解决方法比较笨。就是不在myObj的成员中保存sqliteSql对象。在run 函数中创建一个sqliteSql 对象。这样拷贝myObject进行进程创建的时候,就不涉及sqliteSql的拷贝工作。 代码如下:

from multiprocessing import Pool

# 你自定义的需要反复调用的用于并行计算的进程
class MyClass:
    def __init__(self):
        pass
    def run(self, data):
        sqliteSql = SqliteSql()
        # do some thing
        pass