(1). 先排除主进程无法运行的情况
p = Pool(cpu_core)
parallel_warp = partial(download_oneimg,
id2file=pkl_dic,
save_root=save_root)
while line_idx < npy_array_len:
ann_count = npy_array[line_idx][12]
img_lines = npy_array[line_idx : line_idx+ann_count].tolist() # array 多进程不能用
# 不要直接传字典,要传元素
img_id = img_lines[0][1]
img_ceph_path = pkl_dic[img_id]
# 多进程
p.apply_async(parallel_warp, args=(img_lines, img_ceph_path)
# 单步调试
# parallel_warp( img_lines )
line_idx += ann_count
我一般习惯用 Pool.apply_async(并行函数, args=(并行函数参数们))
来进行并行化操作
所以可以很方便的进行串行程序的调试,直接改成 并行函数(并行函数参数们)
即可
(2). 并行进程数太多导致无法并行的情况
from multiprocessing import Pool
p = Pool(cpu_core). # 尝试将 cpu_core 改成一个较小的值,比如2
我之前并行32进程不行,但是改成16却可以,当然也有可能是各个进程没有准备好的问题,比如将各变量序列化反序列化后拷贝的自己的进程
(3). 是否存在某些难以序列化的数据
假如我们的主进程有一个超级大的 ndarray,如果我们的 Pool 并行进程数是4,则会复制4份同样的 array 到相应的子进程,进程间的数据传输需要做对应数据的 pickle 和 unpickle 操作,同时这个数据也变成了原来的4倍,太占用哥的内存了
如果你暂时无法将各个进程的数据解耦合,只能用相同数据的话,可以用一下Python3.8之后的 multiprocessing.shared_memory
,如果版本小于 3.8 可以参考这位大佬的文章:
Python 多进程共享内存、NumPy 数组
本小节大部分都是摘自此文章,给大佬点赞!!
https://www.7forz.com/3408/
a. Value 与 Array
在 multiprocessing 包中,提供了一些可共享的对象:Value、Array、RawValue 与 RawArray。基本上,前者没有 Raw 的,可以加锁以进行进程间同步,后面 Raw 的没有锁。项目中用到的 numpy 数组都是只读的,子进程只需要读不需要写,所以选择使用 RawArray。
下面的代码会使用一个 numpy array 创建一个 RawArray,然后把它转回 numpy array:
import multiprocessing
import numpy as np
arr = np.zeros(5)
arr_shared = multiprocessing.RawArray('d', arr) # 'd' for double, which is float64
arr_new = np.frombuffer(arr_shared, dtype=np.double)
print(arr_new) # 返回 [0. 0. 0. 0. 0.]
如果 array 是多维的,直接用上面的代码会报错,因为 RawArray 只支持一维。可以这样解决:
import multiprocessing
import numpy as np
SHAPE = (2, 3)
arr = np.zeros(SHAPE)
arr_shared = multiprocessing.RawArray('d', arr.ravel())
arr_new = np.frombuffer(arr_shared, dtype=np.double).reshape(SHAPE)
print(arr_new)
# [[0. 0. 0.]
# [0. 0. 0.]]
b. 传给进程池
思路很清晰:在主进程生成 array,转成 RawArray,再传给 Pool。
然而,直接把 RawArray 对象作为参数是会报错的(RuntimeError: c_double_Array_x objects should only be shared between processes through inheritance)。
在网上找到了答案:通过 pool 的 initializer 实现子进程的初始化。这在官方文档里面只有轻描淡写的一句😂。
具体来说,在创建进程池时,需要传入 initializer 函数与 initargs 参数。
initargs 包含了 RawArray 对象,也可以把它的 shape 也传进去(我下面的参考代码懒就不传了)。
initializer 函数会在子进程创建时被调用,并且把 RawArray 对象变为该子进程的全局变量。
initializer 函数及其对应的变量共享,可以用全局变量或全局的字典来实现:
global_arr_shared = None
def init_pool(arr_shared):
global global_arr_shared
global_arr_shared = arr_shared
而进程池是这样创建的:
with multiprocessing.Pool(processes=2,
initializer=init_pool,
initargs=(arr_shared,)) as pool:
Pool 的 worker 函数中,把 RawArray 转回 numpy array之后,就可以当作普通的 ndarray 操作了。如果修改了数组的内容,也会反映到原数组中,只是需要注意锁的问题。下面是一个很简单的例子。
def worker(i):
arr = np.frombuffer(global_arr_shared, np.double).reshape(SHAPE)
time.sleep(1) # some other operations
return np.sum(arr * i)
总体的程序如下,可以直接运行:
import multiprocessing
import time
import numpy as np
SHAPE = (2, 3)
global_arr_shared = None
def init_pool(arr_shared):
global global_arr_shared
global_arr_shared = arr_shared
def worker(i):
arr = np.frombuffer(global_arr_shared, np.double).reshape(SHAPE)
time.sleep(1) # some other operations
return np.sum(arr * i)
if __name__ == '__main__':
arr = np.random.randn(*SHAPE)
arr_shared = multiprocessing.RawArray('d', arr.ravel())
with multiprocessing.Pool(processes=2,
initializer=init_pool,
initargs=(arr_shared,)) as pool: # initargs传入tuple
for result in pool.map(worker, [1,2,3]):
print(result)
总体来说,就是要先变成 RawArray,然后给 Pool 加上初始化函数以传递 RawArray 给子进程,最后用的时候把 RawArray 转回 numpy array。还是有点麻烦的。
初步测试,性能基本没有受到影响,肯定比multiprocessing.Manager
快。
值得第一提的是,还可以通过这样来获取结果:
for result in pool.map(worker, [1,2,3]):
print(result)
(4). 将难以序列化的数据变成Python的类型
算是(3)的一个拓展,比如(1)中的程序:
img_lines = npy_array[line_idx : line_idx+ann_count]
替换为:
img_lines = npy_array[line_idx : line_idx+ann_count].tolist() # array 多进程不能用
(5). 如果运行过程中出现bug,如何kill
一般运行多进程的程序,我一般会挂在后台:
nohup python download.py &
但是要是这样,子进程报错了,怎么kill呢?
linux批量结束进程指南:
ps -ef | grep "python download.py" | grep -v grep | awk '{print $2}' | xargs kill -9
参考自:
https://unix.stackexchange.com/questions/368639/passing-a-9-to-xargs-kill-command
我就说一下 awk '{print $2}'
这个怎么用:
举个例子:
$ ls -al
我想要第2列数数字怎么搞?
就可以这样:
ls -al | awk '{print $2}'