上篇文章说到,由于Python多线程 multiprocessing 模块中在windows 平台和 Unix 平台系统的底层实现方法不同,所以效率和内存占用会有差别,本文继续记录如何在不同平台实现多线程并行。
Contents
- 基础的多进程调用函数和模块。
- 在类的成员函数中调用多进程。
- 在类的成员函数中调用多进程遇到的一些问题。
如何调用多进程函数
此处以multiprocessing.Pool为例:
file1.py
def target_fun():
return 1
main function:
import multiprocessing
from file1 import target_fun
if __name__ == '__main__':
pool = multiprocessing.Pool(N)
# N为pool的数目,如果空缺则pool的数目为cpu的数目
res = pool.starmap(target_fun, [[] for _ in range(10)])
# pool.starmap 比 pool.map的优点在于,可以输入0~任意个输入参数,而pool.map只有一个参数。后面参数的个数控制了有多少个排队进入pool的子进程
pool.close()
# pool关闭,不再接受新的进程,已有的进程结束后自动退出。
pool.join()
# 父进程在等子进程结束
在类的成员函数中调用multiprocessing.Pool()
class_define.py:
class MC:
def __init__(self):
self.nstep = 30000
# 总计算30000步
def MCstep(self):
self.nstep = self.nstep//10
# 每一个子进程算3000步
do_chunk()
# 搞事情
return res
# 返回结果
def MCseries(self):
pool = multiprocessing.Pool(10)
res_list = pool.starmap(self.MCstep, [[] for _ in range(10)])
# 创建10个子进程,返回并收集结果。
pool.close()
pool.join()
main_fun.py
from class_define import MC
mc_test = MC()
# 实例化类
if __name__ == '__main__':
# 只允许直接调用本函数的进程执行下面的操作,防止子进程循环调用迭代
mc_test.MCseries()
# 调用实例的成员函数
可能的报错
- TypeError: cannot serialize ‘_io.TextIOWrapper’ object
原因:MC类中包含不能被pickled 的成员变量,例如:Files object。导致在实例的成员函数中创建子进程时,无法把当前的实例中的所有成员pickle并复制给子进程。
解决方法:pickle前删掉Files object,在子进程中再重新创建对象。代码:
class_define2.py:
class MC:
def __init__(self):
self.nstep = 30000
# 总计算30000步
self.f = open('haha.txt', 'w')
def __getstate__(self):
"""从字典中删掉self.f"""
state = self.__dict__.copy()
del state['f']
return state
def __setstate__(self, state):
"""Called while unpickling."""
self.__dict__.update(state)
def MCstep(self):
self.f = open('haha.txt', 'w')
"""重新创建文件对象"""
self.nstep = self.nstep//10
# 每一个子进程算3000步
do_chunk()
# 搞事情
return res
# 返回结果
def MCseries(self):
pool = multiprocessing.Pool(10)
res_list = pool.starmap(self.MCstep, [[] for _ in range(10)])
# 创建10个子进程,返回并收集结果。
pool.close()
pool.join()
- 子进程产生的随机结果相同
原因:Windows和Linux创建子进程的过程不完全相同,当Windows创建子进程时,会拷贝并初始化一系列父进程中的变量和对象;而在Linux中,fork出的子进程会完全继承父进程的一系列变量,只有当这些变量更改时才会重新复制出专属于子进程的变量和对象。
同样的道理,再windows中,子进程的random seed会重新初始化,并利用创建的时间作为子进程的随机种子;而在Linux中,子进程完全继承父进程的随机种子,导致所有的子进程都拥有一样的随机种子,所有的random, numpy, scipy 都产生一模一样的随机数。
解决的方法:在子进程中重新调用random.seed() 或 np.random.seed(),并祈祷子进程的创建不要太快(否则导致系统时间相同)
class_define2.py:
class MC:
def __init__(self):
self.nstep = 30000
# 总计算30000步
self.f = open('haha.txt', 'w')
def __getstate__(self):
# 从字典中删掉self.f
state = self.__dict__.copy()
del state['f']
return state
def __setstate__(self, state):
# Called while unpickling.
self.__dict__.update(state)
def MCstep(self):
random.seed()
"""初始化随机种子"""
self.f = open('haha.txt', 'w')
# 重新创建文件对象
self.nstep = self.nstep//10
# 每一个子进程算3000步
do_chunk()
# 搞事情
return res
# 返回结果
def MCseries(self):
pool = multiprocessing.Pool(10)
res_list = pool.starmap(self.MCstep, [[] for _ in range(10)])
# 创建10个子进程,返回并收集结果。
pool.close()
pool.join()