すべてのスレッドが完了するまでpythonマルチスレッド待機


119

これは同様の文脈で尋ねられた可能性がありますが、約20分の検索の後で答えを見つけることができなかったので、尋ねます。

Pythonスクリプト(例:scriptA.py)とスクリプト(例:scriptB.py)を作成しました

scriptBで、異なる引数を使用してscriptAを複数回呼び出します。毎回実行するのに約1時間かかります(その巨大なスクリプトは、多くのことを実行します。心配する必要はありません)。すべての異なる引数を同時に使用する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()ですか?

回答:


150

スクリプトの最後でオブジェクトの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()

したがって、メインスレッドはまで待機しt1、実行t2t3終了します。


5
hmmm-何かを理解するのに問題があります、これは最初にt1を実行するのではなく、終了するまで待ってから、t2..etcなどに進みますか?一度にすべてを実現するにはどうすればよいですか?これが同時にどのように実行されるかわかりませんか?
Inbar Rose 2012

25
joinスレッドの実行が完了するまでブロックの呼び出し。とにかく、すべてのスレッドを待つ必要があります。場合はt1終了すると最初のあなたが待って起動しますt2(既に終了している可能性があり、あなたはすぐに待つために進むであろうt3)。場合は、t1あなたがそれの両方から戻ったとき、実行するのに最長取りt1t2ブロックせずにすぐに戻ります。
Maksim Skurydzin 2012

1
あなたは私の質問を理解していません-上記のコードを私のコードにコピーした場合-それは機能しますか?または私は何かを逃していますか?
インバーローズ2012

2
わかりました。今私は理解していますが、少し混乱していましたが、私は理解していると思います。join現在のプロセスをスレッドにアタッチして、完了するまで待機します。t2がt1の前に終了した場合、t1が完了すると、t2が完了したかどうかを確認します。それがそうであることを確認してから、t3..etc..etc ..をチェックし、すべてが完了したときにのみ続行します。驚くばかり。
インバーローズ2012

3
たとえば、t1が最も時間がかかりますが、t2には例外があります。それから何が起こりますか?その例外をキャッチしたり、t2が正常に終了したかどうかを確認したりできますか?
CiprianTomoiagă2014年

173

スレッドをリストに入れてから、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
はい、それはうまくいくでしょうが、理解するのは難しいです。常にコンパクトなコードと「読みやすさ」のバランスを見つけるようにしてください。覚えておいてください:コードは一度だけ書かれ、何度も読み込まれます。したがって、理解しやすいことがより重要です。
アーロンDigulla

2
「ファクトリー・パターン」は私が一文で説明できるものではありません。グーグルで検索し、stackoverflow.comを検索してください。多くの例と説明があります。簡単に言えば、あなたはあなたのために何か複雑なものを構築するコードを書いています。本物の工場のように:あなたは注文を出し、完成品を取り戻します。
アーロンディグラ

18
副作用のためにリスト内包表記を使用し、結果のリストで有用なことをしないという考えは好きではありません。ループのためのシンプルな、それが二列...広がっていてもきれいになる
ヨアン・アレクサンドルCucu

1
@Aaron DIgull私はそれを理解しています。つまり、私はfor x in threads: x.join()リスト内包表記を使用するのではなく単に実行するということです
Ioan Alexandru Cucu

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

利点の1つは、最大同時ワーカー数を設定してスループットを制御できることです。


しかし、スレッドプールのすべてのスレッドが終了したことをどのようにして知ることができますか?
Prime By Design

1
例でわかるように、withすべてのタスクが終了すると、ステートメントの後のコードが実行されます。
ロベルト

これは機能しません。スレッドで本当に長いことをしてみてください。印刷ステートメントは、スレッドが
完了

@Pranalee、そのコードは機能します。出力行を追加するようにコードを更新しました。すべてのスレッドが終了するまで、「すべてのタスク...」は表示されませんwith。この場合、このようにしてステートメントが機能します。とにかく、いつでもSOで新しい質問を開いてコードを投稿できるので、私たちはあなたのケースで何が起こっているかを見つけるのに役立ちます。
Roberto

あなたが使用することができます@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]

チェックされた答えはうまく説明しますが、これはより短く、醜い繰り返しを必要としません。いい答えです。:)
16

副作用のためだけのリストの理解は通常減価されます*。しかし、この使用例では、それは良い考えのようです。* stackoverflow.com
questions/5753597/…

3
@VinayakKaniyarakkal for t in threads:t.start()いいですね
SmartManoj 2018年

5

以下のようなクラスを作成して、並列に実行する「n」個の関数またはconsole_scriptを追加し、実行を開始して、すべてのジョブが完了するのを待つことができます。

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()。エイリアンスレッドの終了を検出することは不可能であるため、削除されることはありません。

したがって、作成するスレッドのリストを保持する必要がない場合に、これら2つのケースをキャッチするには、次のようにします。

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 2018年

1

forループを使用して作成されたすべてのスレッドを待つ必要があるのと同じ問題に遭遇しました。次のコードを試してみただけです。完璧な解決策ではないかもしれませんが、簡単な解決策だと思いましたテストする:

for t in threading.enumerate():
    try:
        t.join()
    except RuntimeError as err:
        if 'cannot join current thread' in err:
            continue
        else:
            raise
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.