编辑:更新了环境信息(参见第一部分)
我使用的是Python 2.7
Ubuntu 16.04
我有一个应用程序,我已将其简化为三个阶段的过程:
每个阶段都必须完成才能进入下一个阶段,但是每个阶段都包含多个可以并行运行的子任务(我可以发送3个HTTP请求并读取系统日志,同时等待它们返回)
我已将阶段划分为模块,将子任务划分为子模块,因此我的项目层次结构如此:
+ datasources
|-- __init__.py
|-- data_one.py
|-- data_two.py
|-- data_three.py
+ metrics
|-- __init__.py
|-- metric_one.py
|-- metric_two.py
+ outputs
|-- output_one.py
|-- output_two.py
- app.py
app.py
看起来大致如此(伪代码简洁):
import datasources
import metrics
import outputs
for datasource in dir(datasources):
datasource.refresh()
for metric in dir(metrics):
metric.calculate()
for output in dir(outputs):
output.dump()
(包含dir
调用的其他代码忽略了系统模块,还有异常处理等等 - 但这是它的要点)
每个数据源子模块看起来大致如下:
data = []
def refresh():
# Populate the "data" member somehow
data = [1, 2, 3]
return
每个指标子模块看起来大致如下:
import datasources.data_one as data_one
import datasources.data_two as data_two
data = []
def calculate():
# Use the datasources to compute the metric
data = [sum(x) for x in zip(data_one, data_two)]
return
为了并行化第一阶段(数据源),我写了一些简单的内容,如下所示:
def run_thread(datasource):
datasource.refresh()
threads = []
for datasource in dir(datasources):
thread = threading.Thread(target=run_thread, args=(datasource))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
这样可行,之后我可以计算任何指标并填充datasources.x.data
属性
为了并行化第二阶段(指标),因为它更少依赖于I / O而更多地依赖于CPU,我觉得简单的线程实际上不会加速,我需要多处理模块才能利用多个核心。我写了以下内容:
def run_pool(calculate):
calculate()
pool = multiprocessing.Pool()
pool.map(run_pool, [m.calculate for m in dir(metrics)]
pool.close()
pool.join()
此代码运行几秒钟(所以我认为它正在运行?)但是当我尝试时:
metrics.metric_one.data
它返回[]
,就像模块从未运行一样
不知何故,通过使用多处理模块,它似乎是对线程进行限定,以便它们不再共享数据属性。我应该如何重写这一点,以便我可以并行计算每个指标,利用多个核心,但在完成后仍然可以访问数据?
答案 0 :(得分:0)
Process
和Thread
在python中表现完全不同。如果要使用多处理,则需要使用同步数据类型来传递信息。
例如,您可以使用multiprocessing.Array
,可以在您的流程之间共享。
有关详细信息,请参阅文档:https://docs.python.org/2/library/multiprocessing.html#sharing-state-between-processes
答案 1 :(得分:0)
根据评论再次更新:
因为你在2.7,并且你正在处理模块而不是对象,所以你在挑选你需要的东西时遇到了问题。解决方法并不漂亮。它涉及将每个模块的名称传递给您的操作功能。我更新了partial
部分,并进行了更新以删除with
语法。
一些事情:
首先,一般来说,多线程比线程更好。使用线程,您总是冒着处理Global Interpreter Lock的风险,这可能效率极低。如果您使用多核,这将成为一个非问题。
其次,你有正确的概念,但你通过拥有一个全局到模块的数据成员使它变得奇怪。让您的资源返回您感兴趣的数据,并使您的指标(和输出)将数据列表作为输入并输出结果列表。
这会将你的伪代码变成这样的东西:
<强> app.py:强>
import datasources
import metrics
import outputs
pool = multiprocessing.Pool()
data_list = pool.map(lambda o: o.refresh, list(dir(datasources)))
pool.close()
pool.join()
pool = multiprocessing.Pool()
metrics_funcs = [(m, data_list) for m in dir(metrics)]
metrics_list = pool.map(lambda m: m[0].calculate(m[1]), metrics_funcs)
pool.close()
pool.join()
pool = multiprocessing.Pool()
output_funcs = [(o, data_list, metrics_list) for o in dir(outputs)]
output_list = pool.map(lambda o: o[0].dump(o[1], o[2]), output_funcs)
pool.close()
pool.join()
执行此操作后,数据源将如下所示:
def refresh():
# Populate the "data" member somehow
return [1, 2, 3]
您的指标将如下所示:
def calculate(data_list):
# Use the datasources to compute the metric
return [sum(x) for x in zip(data_list)]
最后,您的输出可能如下所示:
def dump(data_list, metrics_list):
# do whatever; you now have all the information
删除数据“全局”并传递它使每个部分更清洁(并且更容易测试)。这突出了使每件作品完全独立。正如你所看到的,我正在做的就是改变传递给map
的列表中的内容,在这种情况下,我通过将它们作为元组传递并将它们解压缩来注入所有先前的计算。功能。当然,你不必使用lambdas。您可以单独定义每个函数,但实际上并没有太多定义。但是,如果确实定义了每个函数,则可以使用partial函数来减少传递的参数数量。我经常使用这种模式,在你更复杂的代码中,你可能需要。这是一个例子:
from functools import partial
do_dump(module_name, data_list, metrics_list):
globals()[module_name].dump(data_list, metrics_list)
invoke = partial(do_dump, data_list=data_list, metrics_list=metrics_list)
with multiprocessing.Pool() as pool:
output_list = pool.map(invoke, [o.__name__ for o in dir(outputs)])
pool.close()
pool.join()
根据评论更新:
使用地图时,您可以保证输入顺序与输出顺序一致,即data_list[i]
是运行dir(datasources)[i].refresh()
的输出。我将这个更改改为 app.py ,而不是将数据源模块导入指标:
data_list = ...
pool.close()
pool.join()
data_map = {name: data_list[i] for i, name in enumerate(dir(datasources))}
然后将data_map传递给每个指标。然后,度量标准按名称获取所需的数据,例如
d1 = data_map['data_one']
d2 = data_map['data_two']
return [sum(x) for x in zip([d1, d2])]