multiprocessing调用map报错cannot pickle MySQL连接对象TypeError
问题原因
Python multiprocessing 模块向进程池投递任务时,会通过pickle对所有传递的对象做序列化,跨进程传输后再反序列化为子进程内的可用对象。
调用p.map(self.GetServerData, self.data)时,传入的是绑定到当前类实例的方法,序列化这个方法会连带序列化绑定的self实例本身。类实例中保存了提前初始化的_mysql_connector.MySQL数据库连接对象,这类对象是底层C实现的网络连接句柄,属于进程独占的内核资源,本身不支持pickle序列化,因此触发报错。
额外说明:就算强行绕过序列化检查,主进程创建的数据库连接、socket、打开的文件句柄这类资源也无法在子进程中正常使用,跨进程共享这类资源本身就是不可行的。
修复方案
根据业务场景选以下任意一种方案即可:
- 方案1:为每个子进程单独初始化数据库连接
利用进程池的initializer参数,在每个子进程启动时独立创建属于自己的MySQL连接,完全避免把主进程的连接对象传递到子进程。示例代码:from multiprocessing import Pool import mysql.connector THREADS_COUNT = 20 # 子进程全局变量存储独立数据库连接 _sub_process_db = None def _init_sub_worker(db_config): # 每个子进程启动时执行,创建仅当前子进程可用的数据库连接 global _sub_process_db _sub_process_db = mysql.connector.connect(**db_config) def _get_server_data_proxy(data_item): # 代理函数,子进程调用时使用自身的数据库连接执行业务逻辑 # 可将原self.GetServerData中依赖数据库的逻辑迁移到此处 # 若需要调用原类方法,可将类实例中不包含数据库连接的部分作为参数传入 pass if __name__ == "__main__": # 初始化进程池时传入初始化函数和数据库配置参数 p = Pool( THREADS_COUNT, initializer=_init_sub_worker, initargs=(your_db_config_dict,) ) raw_stats = p.map(_get_server_data_proxy, self.data) p.close() p.join() - 方案2:自定义类的序列化/反序列化逻辑
如果不想拆分原有类的方法,可以通过实现类的__getstate__和__setstate__魔法方法,自定义序列化时排除不可序列化的数据库连接,在子进程反序列化实例时自动重建连接。示例代码:import mysql.connector class YourDataServiceClass: def __init__(self, db_config): self.db_config = db_config # 主进程初始化时可正常创建连接供主进程使用 self.db_conn = mysql.connector.connect(**self.db_config) # 其他原有初始化逻辑 def __getstate__(self): # 序列化实例时,复制实例属性字典,移除不可序列化的数据库连接对象 state = self.__dict__.copy() del state["db_conn"] return state def __setstate__(self, state): # 子进程反序列化恢复实例时,重建属于当前子进程的数据库连接 self.__dict__.update(state) self.db_conn = mysql.connector.connect(**self.db_config) def GetServerData(self, data_item): # 原有业务逻辑无需修改,正常使用self.db_conn即可 pass - 避坑提示:不要在主进程中提前创建数据库连接后再把实例传入进程池,所有跨进程传递的对象必须是可序列化的,进程独占的IO资源必须在所属进程内单独创建。
内容的提问来源于stack exchange,提问作者Nanda Thota
相关产品推荐
相关产品推荐

