Python使用multiprocessing调用ctypes报can't pickle local object错误求助
问题根因
你遇到的报错本质是multiprocessing的进程间通信依赖pickle序列化,而你在Phasemeter类的__init__方法中提前加载了ctypes.CDLL对象,CDLL内部的函数指针_FuncPtr属于ctypes内部定义的局部对象,不支持pickle序列化。你把整个Phasemeter实例作为参数传入Process.map的时候,序列化步骤就会失败。你之前把lambda改成全局函数没有生效,是因为问题根源不在调用的函数,而在于要传递的参数本身包含不可序列化的属性。
解决方案
最简单的修改方式是延迟加载CDLL动态库,把CDLL加载、函数签名配置的逻辑从__init__方法移到start方法内,这样Phasemeter实例在__init__执行完成后,不会携带任何不可序列化的ctypes对象,序列化传递的时候就不会报错,每个子进程在执行start方法时会独立加载动态库,符合多进程的地址空间隔离规则,也不会影响采集功能的正常运行。
修改后的参考代码如下:
import multiprocessing import os import ctypes # 全局启动函数 def start(Phasemeter_object): Phasemeter_object.start() class Phasemeter : # 所有相位计实例的全局列表 phase_list=[] def __init__(self, name, path_to_so_file="./phasemeter.so", options=None, encoded_path_to_log_file=None, *args,**kwargs) : # 仅存储可序列化的基础配置参数,不加载CDLL self.name = name self.path_to_so_file = path_to_so_file self.options = options if options is not None else [] self.encoded_path_to_log_file = encoded_path_to_log_file self.path=os.path.abspath(os.path.join(os.getcwd(), self.path_to_so_file)) # 将实例加入全局列表 Phasemeter.phase_list.append(self) def start(self): # CDLL加载、签名配置逻辑移到start方法内,由子进程独立执行 self.LIBC = ctypes.CDLL(self.path) self.LIBC.lisaf_phasemeter_main.argtypes= [ctypes.c_int, ctypes.POINTER(ctypes.c_char_p), ctypes.c_char_p,] self.LIBC.lisaf_phasemeter_main.restypes = [ctypes.c_int,] # 原有参数处理、调用C库的逻辑不变 self.c_char_pointer_array = ctypes.c_char_p * len(self.options) self.encoded_options = [str.encode(str(i)) for i in self.options ] self.encoded_options = self.c_char_pointer_array (*self.encoded_options) if self.encoded_path_to_log_file : self.status = self.LIBC.lisaf_phasemeter_main( len(self.encoded_options), self.encoded_options, self.encoded_path_to_log_file) else : self.status = self.LIBC.lisaf_phasemeter_main(len(self.encoded_options), self.encoded_options,None) if __name__ == '__main__' : my_phase1 = Phasemeter(name="PH1") my_phase2 = Phasemeter(name="PH2") with multiprocessing.Pool(len(Phasemeter.phase_list)) as Process : Process.map(start,Phasemeter.phase_list)
如果你确实需要在主进程提前加载CDLL,也可以给Phasemeter类自定义序列化规则,添加__getstate__和__setstate__方法实现:
class Phasemeter: # 原有类逻辑不变,新增以下两个方法 def __getstate__(self): # 序列化时排除不可序列化的CDLL属性 state = self.__dict__.copy() if 'LIBC' in state: del state['LIBC'] return state def __setstate__(self, state): # 反序列化时重新加载CDLL、配置函数签名 self.__dict__.update(state) self.LIBC = ctypes.CDLL(self.path) self.LIBC.lisaf_phasemeter_main.argtypes= [ctypes.c_int, ctypes.POINTER(ctypes.c_char_p), ctypes.c_char_p,] self.LIBC.lisaf_phasemeter_main.restypes = [ctypes.c_int,]
内容的提问来源于stack exchange,提问作者Hugo Castaing
相关产品推荐
相关产品推荐

