Python 使用线程将数组分割成块,对每个块执行计算,并将返回的数组重新组装成一个数组
我有一个很大的python数组,我想把它分解成块,然后对块执行计算,然后“重新组装”成一个数组。下面是我到目前为止所学的内容,我刚刚开始学习一般的线程和Python线程Python 使用线程将数组分割成块,对每个块执行计算,并将返回的数组重新组装成一个数组,python,multithreading,numpy,Python,Multithreading,Numpy,我有一个很大的python数组,我想把它分解成块,然后对块执行计算,然后“重新组装”成一个数组。下面是我到目前为止所学的内容,我刚刚开始学习一般的线程和Python线程 def performCalc(binaryArray): # perform some operation rArray = blah * binaryArray return rArray def main(argv): numberOfThreads = 5 str(len(gr
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.
块的顺序很重要,我必须保持这一点。如果你想用显式的
线程对象来解决它,并且你想得到线程函数的结果,你需要抓住那些线程对象,这样你以后可以连接它们并取出它们的结果。像这样:
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
的默认实现只调用您的target
并丢弃结果。因此,您需要将返回值存储在主线程可以访问的位置。许多numpy程序通过向每个线程传递一个预先分配的数组来实现这一点,这样它们就可以填充它们,这对您的设计没有太大的改变,但这不是您的方向。当然,您可以传入任何其他可变对象以进行突变。或者设置一个全局变量,等等。但是你已经围绕着返回一个值来设计了它,这是一个很好的思考问题的方法,所以让我们坚持下去。最简单的方法是将线程子类化:
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
正确处理超时;在这里,我非常简单,只是在run
方法中添加了\u result=
,并在join
中返回它)
因此:
现在您有了一个可以堆叠在一起的数组列表
然而,我在上面所做的基本上是把每一个线程变成一个半途而废的未来。从概念上讲,只使用实际的期货可能更简单。这意味着我们现在使用的是一个线程池,我们实际上不需要它,因为每个线程只有一个任务。性能成本可能可以忽略不计(您在实际工作上花费的时间比排队要多得多,或者您根本不想以这种方式执行任务),但是,更重要的是,我们增加了大量额外的复杂性(在经过良好测试的stdlib模块中),以降低代码的复杂性;这是否值得,取决于你。无论如何:
with concurrent.futures.ThreadPoolExecutor(max_workers=numberOfThreads) as x:
results = x.map(performCalc, greyScaleChunks)
这将处理创建5个线程,为每个performCalc(chunk)
创建一个作业,将5个作业划分为5个线程,连接线程,并按顺序收集5个作业的结果,因此您所要做的就是将结果堆叠起来
使用执行器的另一个优点是,如果由于GIL,您的代码没有从线程并行性中获益(在您的情况下,这不太可能是一个问题,您应该将大部分时间花在超过20000行的numpy操作上,该操作将在GIL发布后运行,但显然您必须进行测试以验证这一点是否正确),您可以非常轻松地切换到进程:只需将ThreadPoolExecutor
更改为ProcessPoolExecutor
,就完成了
您的参数和返回值可能无法以默认方式在进程之间复制或共享,或者这样做的成本太高,以至于它扼杀了并行性的所有好处,但您可以通过一个单词的更改来测试这一点,然后仅在出现问题时处理它,仍然是一场胜利。您可以使用基本上未记录的线程池(在中提到)及其map\u 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)
打印结果:
[数组([0,2,4,6,8,10,12,14,16,18]),
数组([20,22,24,26,28,30,32,34,36,38]),
数组([40,42,44,46,48,50,52,54,56,58]),
数组([60,62,64,66,68,70,72,74,76,78]),
数组([80,82,84,86,88,90,92,94,96,98])]
在这里,线程是错误的方法。你的程序花在上下文切换上的时间是它实际计算的时间;一次只能运行一个线程。您希望多处理
模块将计算分散到多个进程,这些进程可以在多个处理器/内核上并行执行。@chepner这看起来像是在使用numpy数组。如果是这样的话,他的大部分CPU时间将花费在numpy中,GIL将被解锁,因此线程工作正常。使用concurrent.futures中的ThreadPoolExecutor
可能更简单。实际上,这里不需要线程池,因为每个任务有一个线程,但它仍然可以很容易地分配作业并按顺序收集结果,这正是您遇到问题的地方。@chepner另外,5个线程与5个进程的实际上下文切换是相同的。5个进程在4个核上的竞争将和5个线程一样糟糕,而使用8个核的5个进程将与5个线程一样理想地并行。@chepner是的,线程被限制为单个进程,但每个现代操作系统都在进程内处理多线程,因此线程不限于单个核。在CPython中,如果您在Python本身中执行重要的CPU工作,则在执行每个步骤时需要保持GIL(全局解释器锁),这意味着在任何时候,除了一个线程之外,所有线程都在等待获取该GIL,因此您几乎没有并行性(这些锁操作会增加成本)-但是,如果您在一个长时间释放GIL的C扩展中工作,这不是问题。如果线程池的费用(更概念化而非性能)不是问题,我认为ThreadPoolExecutor
比ThreadPool
(或dummy.pool
)更清晰。不仅仅是因为它有文档记录,而其他的没有(甚至可能在未来的版本中消失)
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)