Python多进程使用partial()时抛出ForkingPickler错误如何修复
问题排查与修复方案
报错根因
该ForkingPickler序列化错误本质是多进程通信时,待序列化的对象中包含了不可被pickle的自定义类实例。你认为入参都是原生字符串,但实际存在隐藏的不可序列化对象进入了序列化流程,常见触发原因有两个:
Entrez.esearch返回的ID列表元素并非纯原生字符串,而是Entrez自定义的XML节点类实例,这类实例序列化时缺少构造必需的tag和attributes参数,触发报错- Linux系统默认使用fork模式启动多进程,主进程调用Entrez接口后产生的全局内存状态会被继承到子进程,其中Entrez依赖的XML解析类实例无法被正确序列化
可行修复步骤
- 先修正代码中隐藏的低级逻辑错误
你当前get_doi函数内存在重复调用read()的错误,会引发后续运行异常,先修正:# 原错误写法 docsum_handle = efetch(db="pubmed", id=pim, retmode='text', rettype='docsum').read() docsum = docsum_handle.read() # 修正后写法,efetch调用read()已经返回字符串,无需二次调用 docsum = efetch(db="pubmed", id=pim, retmode='text', rettype='docsum').read() - 修改多进程启动模式,规避fork的状态继承问题
在if __name__ == '__main__'代码块首行添加启动方式配置,改用spawn模式启动进程:if __name__ == '__main__': import multiprocessing as mp mp.set_start_method('spawn') # 新增此行 # 后续原有代码 - 强制转换ID列表为原生字符串类型
拿到ID列表后统一转成Python原生字符串,彻底排除Entrez自定义类型的影响:pim_list = get_pim_with_regex_list(keywords) pim_list = [str(pmid) for pmid in pim_list] # 新增此行做类型转换 - 更优替代方案:改用多线程实现
爬虫属于IO密集型任务,大部分时间在等待网络响应,多线程的运行效率不低于多进程,还能完全规避序列化问题,替换代码如下:if __name__ == '__main__': from functools import partial from concurrent.futures import ThreadPoolExecutor import multiprocessing as mp keywords = ["keyword1", "keyword2"] pim_list = get_pim_with_regex_list(keywords) pim_list = [str(pmid) for pmid in pim_list] regex = "keyword1 keyword2" worker_fn = partial(get_doi, regex=regex) # 线程数可以设为CPU核心数的2-4倍,充分利用IO等待时间 with ThreadPoolExecutor(max_workers=mp.cpu_count()*2) as pool: entries = list(pool.map(worker_fn, pim_list))
内容的提问来源于stack exchange,提问作者Hymir
相关产品推荐
相关产品推荐

