Python多进程场景下为何无法在__init__方法中以self.fh形式绑定文件句柄?
环境信息
- 操作系统:
Linux debian 5.10.0-8-amd64 #1 SMP Debian 5.10.46-4 (2021-08-03) x86_64 GNU/Linux - Python版本:
Python 3.9.2
问题重现
正常运行的代码
当用局部变量保存文件句柄时,多进程任务可以正常执行(注意原代码漏了import time,实际运行需要补上):
from multiprocessing.pool import Pool import time class my_mp(object): def __init__(self): self.process_num = 3 fh = open('test.txt', 'w') # 局部变量存储文件句柄 def run_task(self,i): print('process {} start'.format(str(i))) time.sleep(2) print('process {} end'.format(str(i))) def run(self): pool = Pool(processes = self.process_num) for i in range(self.process_num): pool.apply_async(self.run_task,args = (i,)) pool.close() pool.join() # 执行后有正常输出 ins = my_mp() ins.run()
输出结果:
process 0 start process 1 start process 2 start process 0 end process 2 end process 1 end
异常无输出的代码
当把文件句柄绑定为实例属性self.fh后,运行时无任何输出:
from multiprocessing.pool import Pool import time class my_mp(object): def __init__(self): self.process_num = 3 self.fh = open('test.txt', 'w') # 实例属性存储文件句柄 def run_task(self,i): print('process {} start'.format(str(i))) time.sleep(2) print('process {} end'.format(str(i))) def run(self): pool = Pool(processes = self.process_num) for i in range(self.process_num): pool.apply_async(self.run_task,args = (i,)) pool.close() pool.join() # 执行后无任何输出 ins = my_mp() ins.run()
问题原因拆解
这个坑我之前踩过,核心是多进程场景下的对象序列化问题,结合Linux系统的进程创建方式来解释:
在Linux中,multiprocessing.Pool默认用fork方式创建子进程。当你用apply_async提交实例方法self.run_task时,Python需要把整个my_mp实例序列化(也就是用pickle处理)后传递给子进程——哪怕你从来没在子进程里用到self.fh。
而文件句柄是操作系统级别的资源,无法被pickle序列化,它和父进程的进程ID绑定,不能被复制到子进程中。当Python尝试序列化包含self.fh的实例时,会在后台抛出序列化错误,但apply_async默认不会主动暴露这类错误(除非你调用任务的get()方法获取结果),所以你看不到报错,子进程直接启动失败,自然没有任何输出。
解决方案
针对这个问题,有几种实用的解决办法:
- 避免将文件句柄绑定为实例属性:如果不需要在其他方法中复用这个文件句柄,就用局部变量,最好配合
with语句自动管理文件关闭:def __init__(self): self.process_num = 3 with open('test.txt', 'w') as fh: # 在这里完成文件写入操作,with块结束自动关闭文件 pass - 延迟初始化文件句柄:如果确实需要在实例中保留文件句柄,只在实际需要使用它的方法中初始化,而不是在
__init__里:class my_mp(object): def __init__(self): self.process_num = 3 self.fh = None def write_content(self, content): if not self.fh: self.fh = open('test.txt', 'w') self.fh.write(content) # 其他方法保持不变... - 切换进程创建方式为spawn:spawn方式会重新启动Python解释器,不会复制父进程的内存空间,而是序列化传递必要对象,避免文件句柄的问题(但这种方式开销比fork大):
def run(self): from multiprocessing import get_context pool = Pool(processes=self.process_num, context=get_context('spawn')) for i in range(self.process_num): pool.apply_async(self.run_task,args = (i,)) pool.close() pool.join()
内容的提问来源于stack exchange,提问作者showkey
相关产品推荐
相关产品推荐

