![](/img/trans.png)
[英]python webscraping using multiprocessing and list comprehension very slow
[英]Python multiprocessing problem using list comprehension
我无法理解为什么不更新多处理、列表理解输入,特别是下面示例代码中的“val2chg”条目。 我不会对此感到惊讶,除非它在没有多处理的情况下工作正常。 如果在没有列表理解的情况下传递“相同”列表,则多处理代码是成功的。 我敢肯定其他人也遇到过这个问题,但我无法正确地表达我的搜索词以提供指向答案的链接。
下面是一个简单的例子,结果后跟代码。
The input (sld) is
[{'val': 0, 'val2chg': 'not'}, {'val': 1, 'val2chg': 'not'}]
The four results are
multiproc: True. listcomp: True, [{'val': 0, 'val2chg': 'not'}, {'val': 1, 'val2chg': 'not'}]
multiproc: False. listcomp: True, [{'val': 0, 'val2chg': 'changed'}, {'val': 1, 'val2chg': 'changed'}]
multiproc: False. listcomp: False, [{'val': 0, 'val2chg': 'changed'}, {'val': 1, 'val2chg': 'changed'}]
multiproc: True. listcomp: False, [{'val': 0, 'val2chg': 'changed'}, {'val': 1, 'val2chg': 'changed'}]
import multiprocessing as mp
sld = [{'val':0, 'val2chg':'not'}, {'val':1, 'val2chg':'not'}]
print(sld)
def update_values(sld, domp):
global update
def update(idx, some):
some['val2chg'] = 'changed'
return idx, some
if domp:
pool = mp.Pool()
results = [pool.apply_async(update, (idx, sld[idx])) for idx in range(len(sld))]
pool.close()
pool.join()
for result in results:
idx = result.get()[0]
sld[idx] = result.get()[1]
else:
for idx in range(len(sld)):
results = update(idx, sld[idx])
idx = results[0]
sld[idx] = results[1]
domp = True
listcomp = False
if listcomp:
update_values([s for idx, s in enumerate(sld)], domp)
else:
update_values(sld, domp)
print(f'multiproc: {domp}. listcomp: {listcomp}, {sld}')
我将尽力解释这一点:
首先你的声明:
if listcomp:
update_values([s for idx, s in enumerate(sld)], domp)
可以简化为:
if listcomp:
update_values([s for s in sld)], domp)
这相当于:
if listcomp:
arg = [s for s in sld)]
update_values(arg, domp)
但是您现在要做的是向update_values
传递一个列表arg
,它是列表sld
的浅表副本,而不是sld
本身,其中arg[0] == sld[0]
,即每个列表都引用相同的字典元素。
在调用update
的非多处理情况下,您将传递对arg
或sld
的每个元素的引用,具体取决于domp
的值。 但在任何一种情况下,它们都是sld
中包含的相同字典引用。 因此update
中的代码some['val2chg'] = 'changed'
实际上是在原地更新sld
引用的字典(同样,它是arg
引用的相同字典)。 因此,即使listcomp
为 true 并且update
的返回值被用于更新过去的arg
列表而不是sld
列表,也没关系,因为sld
引用的字典元素已经由update
。
但是,在多处理情况下,现在传递给update
的是字典引用的序列化/反序列化副本。 语句some['val2chg'] = 'changed'
正在修改该副本,该副本位于执行该语句的池进程的地址空间中。 所以我们现在必须依靠update_values
使用 update 的返回值来update
传递的sld
参数,不幸的是,它不是全局sld
列表,而是它的副本arg
。
以下是更新的代码,与您的代码不同,它仅适用于默认情况下使用fork方法创建新进程的平台(例如 Linux),应该适用于所有平台。 我还修改了间距以符合 PEP8 指南:
import multiprocessing as mp
def update(idx, some):
some['val2chg'] = 'changed'
return idx, some
def update_values(sld, domp):
if domp:
pool = mp.Pool()
results = [pool.apply_async(update, (idx, sld[idx])) for idx in range(len(sld))]
pool.close()
pool.join()
for result in results:
idx, some = result.get()
sld[idx] = some
else:
for idx in range(len(sld)):
results = update(idx, sld[idx])
idx = results[0]
sld[idx] = results[1]
if __name__ == '__main__':
domp = True
listcomp = True
sld = [{'val':0, 'val2chg':'not'}, {'val':1, 'val2chg':'not'}]
print(sld)
if listcomp:
#arg = [s for idx, s in enumerate(sld)]
# The above can be simplified to:
arg = [s for s in sld]
# But now arg is a copy of sld and it is the copy that is being modified
update_values(arg, domp)
# We now need this (but we really shouldn't be using a list comprehension at all):
sld = arg
else:
update_values(sld, domp)
print(f'multiproc: {domp}. listcomp: {listcomp}, {sld}')
印刷:
[{'val': 0, 'val2chg': 'not'}, {'val': 1, 'val2chg': 'not'}]
multiproc: True. listcomp: True, [{'val': 0, 'val2chg': 'changed'}, {'val': 1, 'val2chg': 'changed'}]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.