Python中处理线程联接的正确方法

Python中处理线程联接的正确方法,python,multithreading,join,spawn,Python,Multithreading,Join,Spawn,因此,我编写了一个工具,它获取项目列表,将其拆分为给定数量的列表(假设为10个),然后获取这10个列表并生成10个线程,“EvaluationThreads”(扩展threading.thread),这些线程中的每一个都对提供给它们进行评估的任何线程进行评估。当我启动每个线程时,我将它们全部放入一个列表中,在生成它们之后,我有以下代码: for th in threadList: th.join() someTotal = th.resultsAttribute 这就是我处理等

因此,我编写了一个工具,它获取项目列表,将其拆分为给定数量的列表(假设为10个),然后获取这10个列表并生成10个线程,“EvaluationThreads”(扩展threading.thread),这些线程中的每一个都对提供给它们进行评估的任何线程进行评估。当我启动每个线程时,我将它们全部放入一个列表中,在生成它们之后,我有以下代码:

for th in threadList:
    th.join()
    someTotal = th.resultsAttribute
这就是我处理等待所有线程完成并收集其信息的方式。虽然这是一种等待所有事情完成然后收集结果的工作方式,但我觉得必须有一种更优雅的方式来完成,因为这些线程可能在不同的时间完成,如果第一个开始的线程最后完成,那么之前完成的所有线程都必须等待该线程完成,然后才能完成加入。有没有一种方法可以获取这些线程的信息,并在它们完成时连接它们,而不是按照它们开始的顺序连接它们?我最初认为我会在线程中使用某种回调之类的方法,但我不确定是否有一种更可接受的解决方案

谢谢你的帮助


编辑:澄清一下,我的计算函数没有CPU限制,我也没有试图在线程之间分发文档以尽快完成,每个线程都有固定的偶数个作业。

一旦可用,就使用队列从线程中推出信息:

假设这是您的线程:

class myThread(threading.Thread):
   def __init__(self, results_queue):
       self.results_queue = results_queue
       #other init code here


   def run(self):
       #thread code here

       self.results_queue.put(result) #result is the information you want from the thread
这是您的主要代码:

import Queue #or "import queue" in Python 3.x
results_queue = Queue()

#thread init code here

for i in xrange(num_threads_running):
    data = results_queue.get() # queue.get() blocks until some item is available
    #process data as it is made available

#at this point, there is no need to .join(), since all the threads terminate as soon as they put data to the queue.

关于你的主要问题:

如果您正在做比这更复杂的事情,或者,特别是,如果您反复做这件事,您可能需要一个“线程组”类。有几十个是预先制作好的,但是如果你不喜欢其中任何一个的话,自己写一个就很简单了

然后,与此相反:

threadList = []
for argchunk in splitIntoChunks(values, 10):
  threadList.append(threading.Thread(target=myThreadFunc, args=argchunk))
...
someTotal = 0
for th in threadList:
  th.join()
  someTotal += th.resultsAttribute
您可以这样做:

threadGroup = ThreadGroup.ThreadGroup()
for argchunk in splitIntoChunks(values, 10):
  threadGroup.newThread(myThreadFunc, argchunk)
threadGroup.join()
someTotal = sum(th.resultsAttribute for th in threadGroup)
pool = ThreadPool(10)
for argchunk in splitIntoChunks(values, 100):
  pool.putRequest(myThreadFunc, argchunk)
pool.wait()
或者,也许更好,一个完整的线程池库,所以您可以这样做:

threadGroup = ThreadGroup.ThreadGroup()
for argchunk in splitIntoChunks(values, 10):
  threadGroup.newThread(myThreadFunc, argchunk)
threadGroup.join()
someTotal = sum(th.resultsAttribute for th in threadGroup)
pool = ThreadPool(10)
for argchunk in splitIntoChunks(values, 100):
  pool.putRequest(myThreadFunc, argchunk)
pool.wait()
这里的优点是,您可以轻松地在10个线程上安排100个适当的作业,而不是每个线程安排10个作业,而无需维护队列等所有工作。缺点是您不能只迭代线程来获取返回值,您必须迭代作业,理想情况下,您不想让作业一直保持活动状态直到结束,只是为了可以迭代它们

这就引出了第二个问题,如何从线程(或作业)中获取值。有很多很多方法可以做到这一点

你所做的很管用。你甚至不需要任何锁

正如您所建议的那样,使用回调也是有效的。但是请记住,回调将在工作线程上运行,而不是在主线程上运行,因此如果它正在访问某个全局对象,则需要某种同步

如果无论如何都要进行同步,那么回调可能没有任何好处。例如,如果您所要做的只是对一组值求和,那么您可以只设置
total=[0]
,并让每个线程在锁中执行
total[0]+=myValue
。(当然,在这种情况下,只在主线程中求和并避免锁定可能更有意义,但如果合并结果的工作更为繁重,那么选择可能就不那么简单了。)

您还可以使用某种原子对象,而不是显式锁定。例如,标准的Queue.Queue和collections.deque都是原子的,因此每个线程都可以设置
q=Queue.Queue()
,然后每个线程通过执行
q.push(myValue)
来推送其结果,然后加入后只需迭代并汇总队列的值

事实上,如果每个线程只向队列推送一次,您只需在队列本身上执行10次阻塞,之后您就知道
group.join()
pool.wait()
或任何快速返回的操作

或者,您甚至可以将回调作为作业推送到队列中。同样,您可以对队列执行10次阻塞,每次执行结果


如果每个线程可以返回多个对象,那么它们可以在完成后将sentinel值或回调推送到队列中,并且主线程会一直弹出,直到它读取10个sentinel。

为什么您的问题会成为问题?一个已经完成但尚未加入的线程浪费了很少的资源(基本上,是操作系统在内核或用户空间的某个地方维护的表中的一个小条目)。我想这并不完全是一个问题,但这似乎是一个非常不雅的解决方案,如果空闲线程不是一个等待加入的问题,我想我就不会担心了。附带说明:如果您的“评估”操作是CPU受限的,那么您可能不会从这个应用程序中使用线程中获得太多好处。阅读关于CPython的全局解释器锁(GIL)。@NedBatchelder:+1。如果数据块的复制成本很低(或者可以由线程本身生成而不是传入),那么最好使用多处理而不是线程。这也意味着数据在默认情况下是非共享的(因此,如果你搞砸了,它会更明显,灾难性也更小)。多处理有一个内置的Pool类,有很好的方法来简化最常见的习惯用法;它们是完整的线程。尽快加入他们很好,但这意味着你需要一些方法来知道哪一个需要先加入。(或者你需要一个Win32的WaitForMultipleObjects的跨平台等价物,你没有……虽然一个合适的线程组/池库可能会使用每个平台上可用的最佳实现。)我相信只有当你明确加入一个线程或者线程对象被破坏时,基础系统资源才会被释放,这意味着需要调用join(),除非您要退出,或者您有with块或等效块来管理线程对象。@abarnert:您有链接吗?我找不到关于他的任何东西