使用线程将数组切片为块并对每个块执行计算,并将返回的数组重新组合成一个数组

时间:2018-03-19 16:46:26

标签: python multithreading numpy

我有一个大的python数组,我想分成几个块,然后在块上执行计算,然后“重新组装”到一个数组中。下面是我到目前为止我刚刚开始学习一般的线程和使用Python进行线程化。

def performCalc(binaryArray):

    # perform some operation
    rArray = blah * binaryArray
    return rArray

def main(argv):
    numberOfThreads = 5
    str(len(grey_arr) # 100,000 elements
    greyScaleChunks = np.array_split(grey_arr, numberOfThreads)
    for i in range(numberOfThreads):
        t = Thread(target=performCalc, args=(greyScaleChunks[i],))
        t.start()

    # take all the return values and later create one big array to be resized into matrix.

块的排序很重要,我必须保持这一点。

2 个答案:

答案 0 :(得分:0)

如果要使用显式Thread对象解决它,并且想要获取线程函数的结果,则需要保留这些Thread对象,以便稍后{{1}他们并取出他们的结果。像这样:

join

此外,ts = [] for i in range(numberOfThreads): t = Thread(target=performCalc, args=(greyScaleChunks[i],)) ts.append(t) t.start() for t in ts: t.join() # When you get here, all threads have finished 的默认实现只会调用您的Thread.run并丢弃结果。因此,您需要将返回值存储在主线程可以访问的某个位置。许多numpy程序通过将预先分配的数组传递给每个线程来实现这一点,因此他们可以填充它们,这对您的设计来说并不是太大的变化,但它不是您的方式# 39;重新开始。您当然可以传入任何其他可变对象进行变异。或者设置一个全局变量等等。但是你已经围绕返回一个值设计了这个,这是思考事物的好方法,所以让我们坚持下去。实现这项工作的最简单方法是继承target

Thread

这是未经测试的代码,但应该可以使用。 (我在实际代码中做了类似的事情,但更复杂,允许class ReturningThread(threading.Thread): def run(self): try: if self._target: self._result = self._target(*self._args, **self._kwargs) finally: del self._target, self._args, self._kwargs def join(self): super().join() return self._result 正确处理超时;在这里我保持简单,只需在join中添加_result = }方法和run中的return。)

所以:

join

现在你有一个可以堆叠在一起的数组列表。

然而,我上面所做的基本上是将每个线程变成半个未来。仅使用实际期货在概念上可能更简单。这确实意味着我们现在正在使用我们并不真正需要的线程池 - 每个线程只有一个任务。这可能是一个可以忽略不计的性能成本(你在实际工作上花费的时间比排队要多得多,或者你不想在第一时间以这种方式进行编程),但是,更多重要的是,我们在引擎盖下(在经过充分测试的stdlib模块中)添加了显着的额外复杂性,以降低代码的复杂性;是否值得,取决于你。无论如何:

ts = []
for i in range(numberOfThreads):
    t = ReturningThread(target=performCalc, args=(greyScaleChunks[i],))
    ts.append(t)
    t.start()
results = []
for t in ts:
    results.append(t.join())

这可以处理创建5个线程,为每个with concurrent.futures.ThreadPoolExecutor(max_workers=numberOfThreads) as x: results = x.map(performCalc, greyScaleChunks) 创建一个作业,将5个作业分成5个线程,加入线程,以及收集5个作业'按顺序排列,所以你所要做的就是叠加结果。

使用执行程序的另一个好处是,如果事实证明你的代码并没有因为GIL而受益于线程并行(在你的情况下不太可能出现问题 - 你应该花费大部分时间在一个超过20000行的numpy操作,它将在GIL发布的情况下运行 - 但显然你必须进行测试才能验证是否真实),你可以非常轻松地切换到进程:只需将performCalc(chunk)更改为{ {1}}你已经完成了。

你的args和return可能无法以默认方式在进程之间复制或共享,或者这样做是如此昂贵以至于它会杀死并行性的所有好处 - 但事实是你可以用一个单词的变化进行测试,然后只有在遇到问题时才处理,仍然是一个胜利。

答案 1 :(得分:0)

您可以使用大部分未记录的ThreadPool(在this answer中提及)及其map_async()方法来执行此操作,如以下可运行示例所示:

import numpy as np
from pprint import pprint
from multiprocessing.pool import ThreadPool
import threading

blah = 2

def performCalc(binaryArray):
    # perform some operation
    rArray = blah * binaryArray
    return rArray

def main(data_array):
    numberOfThreads = 5
    pool = ThreadPool(processes=numberOfThreads)

    greyScaleChunks = np.array_split(data_array, numberOfThreads)
    results = pool.map_async(performCalc, greyScaleChunks)
    pool.close()
    pool.join()  # Block until all threads exit.

    # Final results will be a list of arrays.
    pprint(results.get())

grey_arr = np.array(range(50))
main(grey_arr)

打印结果:

[array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18]),
 array([20, 22, 24, 26, 28, 30, 32, 34, 36, 38]),
 array([40, 42, 44, 46, 48, 50, 52, 54, 56, 58]),
 array([60, 62, 64, 66, 68, 70, 72, 74, 76, 78]),
 array([80, 82, 84, 86, 88, 90, 92, 94, 96, 98])]