python multithreading等到所有线程完成


119

可能是在类似的情况下提出的,但是经过大约20分钟的搜索,我无法找到答案,所以我会提出。

我写了一个Python脚本(让我们说:scriptA.py)和一个脚本(让我们说scriptB.py)。

在scriptB中,我想使用不同的参数多次调用scriptA,每次运行大约需要一个小时(它是一个巨大的脚本,做了很多事情。。不用担心),并且我希望能够运行scriptA同时具有所有不同的参数,但是我需要等到所有参数都完成后才能继续;我的代码:

import subprocess

#setup
do_setup()

#run scriptA
subprocess.call(scriptA + argumentsA)
subprocess.call(scriptA + argumentsB)
subprocess.call(scriptA + argumentsC)

#finish
do_finish()

我想同时运行所有程序subprocess.call(),然后等到它们全部完成后,该怎么办?

我试图像这里的例子一样使用线程:

from threading import Thread
import subprocess

def call_script(args)
    subprocess.call(args)

#run scriptA   
t1 = Thread(target=call_script, args=(scriptA + argumentsA))
t2 = Thread(target=call_script, args=(scriptA + argumentsB))
t3 = Thread(target=call_script, args=(scriptA + argumentsC))
t1.start()
t2.start()
t3.start()

但是我认为这是不对的。

我怎么知道他们都已经跑完了才去我家do_finish()

Answers:


150

您需要在脚本末尾使用object的join方法Thread

t1 = Thread(target=call_script, args=(scriptA + argumentsA))
t2 = Thread(target=call_script, args=(scriptA + argumentsB))
t3 = Thread(target=call_script, args=(scriptA + argumentsC))

t1.start()
t2.start()
t3.start()

t1.join()
t2.join()
t3.join()

因此,主线程将等到t1t2t3完成执行。


5
hmmm-无法理解某些内容,请先运行t1,直到完成,然后再转到t2..etc等吗?如何使这一切同时发生?我不知道这将如何同时运行它们?
Inbar Rose

25
join块的调用直到线程完成执行为止。无论如何,您将不得不等待所有线程。如果t1首先完成,您将开始等待t2(可能已经完成,您将立即等待t3)。如果t1把最长的执行,当你从它都返回t1,并t2会立即返回而不阻塞。
Maksim Skurydzin 2012年

1
您不理解我的问题-如果我将上面的代码复制到我的代码中,它将起作用吗?还是我错过了什么?
Inbar Rose

2
好吧,我知道了 现在我明白了,对此有点困惑,但是我想我明白,join将当前进程附加到线程上,等到完成为止,如果t2在t1之前完成,那么当t1完成时它将检查t2是否完成,请参见就是这样,然后检查t3..etc..etc ..然后只有完成所有操作后,它才会继续。太棒了
Inbar Rose

3
说t1花费的时间最长,但是t2有一个例外。那会发生什么呢?您可以捕获该异常或检查t2是否完成正常吗?
CiprianTomoiagă14年

171

将线程放入列表中,然后使用Join方法

 threads = []

 t = Thread(...)
 threads.append(t)

 ...repeat as often as necessary...

 # Start all threads
 for x in threads:
     x.start()

 # Wait for all of them to finish
 for x in threads:
     x.join()

1
是的,这行得通,但很难理解。您应该始终尝试在紧凑代码和“可读性”之间找到平衡。切记:代码只能编写一次,但可以读取多次。因此,易于理解更为重要。
亚伦·迪古拉

2
我不能用一句话来解释“工厂模式”。Google并搜索stackoverflow.com。有很多示例和解释。简而言之:您编写的代码会为您构建一些复杂的东西。就像真正的工厂一样:您下订单并取回成品。
亚伦·迪古拉

18
我不喜欢使用列表理解的副作用,并且不对结果列表做任何有用的事情。一个简单的for循环即使散布了两行也会更干净...
Ioan Alexandru Cucu 2014年

1
@Aaron DIgull我明白这一点。我的意思是,我只会做一个for x in threads: x.join()而不是使用列表理解的方法
Ioan Alexandru

1
@IoanAlexandruCucu:我一直在想,如果有一个更好的可读性和有效的解决方案:stackoverflow.com/questions/21428602/...
亚伦Digulla

29

在Python3中,由于使用Python 3.2,因此有一种新方法可以达到相同的结果,我个人更喜欢传统的线程创建/启动/连接程序包concurrent.futureshttps : //docs.python.org/3/library/concurrent.futures .html

使用ThreadPoolExecutor代码将是:

from concurrent.futures.thread import ThreadPoolExecutor
import time

def call_script(ordinal, arg):
    print('Thread', ordinal, 'argument:', arg)
    time.sleep(2)
    print('Thread', ordinal, 'Finished')

args = ['argumentsA', 'argumentsB', 'argumentsC']

with ThreadPoolExecutor(max_workers=2) as executor:
    ordinal = 1
    for arg in args:
        executor.submit(call_script, ordinal, arg)
        ordinal += 1
print('All tasks has been finished')

先前代码的输出类似于:

Thread 1 argument: argumentsA
Thread 2 argument: argumentsB
Thread 1 Finished
Thread 2 Finished
Thread 3 argument: argumentsC
Thread 3 Finished
All tasks has been finished

优点之一是,您可以控制吞吐量,设置最大并行工作器数。


但是如何知道线程池中的所有线程何时完成?
由Design Prime设计

1
如您在示例中所见,with所有任务完成后,将在语句后执行代码。
罗伯托

这行不通。尝试在线程中做很长时间的事情。您的打印语句将在线程完成之前执行
Pranalee

@Pranalee,该代码有效,我已经更新了代码以添加输出行。在所有线程完成之前,您将看不到“所有任务...”,with在这种情况下,这是语句按设计方式工作的方式。无论如何,您总是可以在SO中打开一个新问题并发布您的代码,以便我们可以帮助您了解所发生的情况。
罗伯托

@PrimeByDesign你可以使用concurrent.futures.wait的功能,你可以看到在这里真实的例子 :官方文档docs.python.org/3/library/...
亚历山大·福尔廷

28

我更喜欢根据输入列表使用列表理解:

inputs = [scriptA + argumentsA, scriptA + argumentsB, ...]
threads = [Thread(target=call_script, args=(i)) for i in inputs]
[t.start() for t in threads]
[t.join() for t in threads]

选中的答案可以很好地解释,但是该答案更短,并且不需要难看的重复。只是一个很好的答案。:)
tleb

仅针对副作用的列表理解通常会降低*。但是在这种用例中,这似乎是一个好主意。* stackoverflow.com
questions/5753597/…

3
@VinayakKaniyarakkal for t in threads:t.start()不是更好吗?
SmartManoj

5

您可以在下面使用类似的类,从中可以添加要并行执行的n个功能或console_scripts,并开始执行并等待所有作业完成。

from multiprocessing import Process

class ProcessParallel(object):
    """
    To Process the  functions parallely

    """    
    def __init__(self, *jobs):
        """
        """
        self.jobs = jobs
        self.processes = []

    def fork_processes(self):
        """
        Creates the process objects for given function deligates
        """
        for job in self.jobs:
            proc  = Process(target=job)
            self.processes.append(proc)

    def start_all(self):
        """
        Starts the functions process all together.
        """
        for proc in self.processes:
            proc.start()

    def join_all(self):
        """
        Waits untill all the functions executed.
        """
        for proc in self.processes:
            proc.join()


def two_sum(a=2, b=2):
    return a + b

def multiply(a=2, b=2):
    return a * b


#How to run:
if __name__ == '__main__':
    #note: two_sum, multiply can be replace with any python console scripts which
    #you wanted to run parallel..
    procs =  ProcessParallel(two_sum, multiply)
    #Add all the process in list
    procs.fork_processes()
    #starts  process execution 
    procs.start_all()
    #wait until all the process got executed
    procs.join_all()

这是多处理。问题是关于docs.python.org/3/library/threading.html
Rustam A.

3

threading 模块文档

有一个“主线程”对象;这对应于Python程序中的初始控制线程。它不是守护程序线程。

有可能会创建“虚拟线程对象”。这些是与“外来线程”相对应的线程对象,“外来线程”是在线程模块外部启动的控制线程,例如直接从C代码启动的线程。虚拟线程对象的功能有限。它们始终被认为是活动的和守护程序的,不能被join()编辑。它们永远不会被删除,因为不可能检测到外来线程的终止。

因此,要在不希望保留所创建线程的列表的情况下捕获这两种情况:

import threading as thrd


def alter_data(data, index):
    data[index] *= 2


data = [0, 2, 6, 20]

for i, value in enumerate(data):
    thrd.Thread(target=alter_data, args=[data, i]).start()

for thread in thrd.enumerate():
    if thread.daemon:
        continue
    try:
        thread.join()
    except RuntimeError as err:
        if 'cannot join current thread' in err.args[0]:
            # catchs main thread
            continue
        else:
            raise

于是:

>>> print(data)
[0, 4, 12, 40]

2

也许像

for t in threading.enumerate():
    if t.daemon:
        t.join()

我已经尝试过此代码,但不确定其工作原理,因为我的代码的最后一条指令已打印在此for循环之后,但该过程仍未终止。
Omkar '18

1

我遇到了同样的问题,我需要等待使用for循环创建的所有线程。我只是尝试了以下代码段,它可能不是完美的解决方案,但我认为这将是一个简单的解决方案去测试:

for t in threading.enumerate():
    try:
        t.join()
    except RuntimeError as err:
        if 'cannot join current thread' in err:
            continue
        else:
            raise
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.