파이썬 멀티 스레딩은 모든 스레드가 끝날 때까지 기다립니다.


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()있습니까?

답변:


150

스크립트의 끝에서 개체의 결합 방법 을 사용해야 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, t2그리고 t3실행을 완료합니다.


5
hmmm-무언가를 이해하는 데 문제가 있습니다. 처음에는 t1을 실행하고 완료 될 때까지 기다린 다음 t2..etc로 이동하지 않습니까? 이 모든 것을 한꺼번에 어떻게 만들 수 있습니까? 나는 이것이 동시에 어떻게 실행되는지 보지 못합니까?
Inbar Rose 2012-08-15

25
join스레드가 실행을 마칠 때까지 블록에 대한 호출 입니다. 어쨌든 모든 스레드를 기다려야합니다. t1먼저 완료 되면 기다릴 것입니다 t2(이미 완료되었을 수 있으며 즉시 기다릴 것입니다 t3). 경우 t1당신이 그것을 모두에서 돌아 왔을 때, 실행하는 데 가장 긴했다 t1t2차단하지 않고 즉시 반환됩니다.
Maksim Skurydzin 2012 년

1
내 질문을 이해하지 못합니다-위 코드를 내 코드에 복사하면 작동합니까? 아니면 내가 뭔가를 놓치고 있습니까?
Inbar Rose 2012-08-15

2
알았어. 이제 나는 이해하고 약간 혼란 스러웠지만 이해한다고 생각 join합니다. 현재 프로세스를 스레드에 연결하고 완료 될 때까지 기다립니다 .t2가 t1 전에 완료되면 t1이 완료되면 t2가 완료되었는지 확인합니다. 그런 다음 t3..etc..etc ..를 확인한 다음 모든 작업이 완료 될 때만 계속됩니다. 대박.
Inbar Rose 2012-08-15

3
t1이 가장 오래 걸리지 만 t2에는 예외가 있습니다. 그러면 어떻게됩니까? 해당 예외를 포착하거나 t2가 정상적으로 완료되었는지 여부를 확인할 수 있습니까?
Ciprian Tomoiagă 2014 년

174

스레드를 목록에 넣은 다음 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
예, 작동하지만 이해하기 더 어렵습니다. 항상 간결한 코드와 "가독성"사이의 균형을 찾으려고 노력해야합니다. 기억하십시오 : 코드는 한 번 작성되지만 여러 번 읽습니다. 따라서 이해하기 쉬운 것이 더 중요합니다.
Aaron Digulla 2012-08-15

2
"공장 패턴"은 제가 한 문장으로 설명 할 수있는 것이 아닙니다. Google에서 검색하고 stackoverflow.com을 검색하십시오. 많은 예와 설명이 있습니다. 간단히 말해서, 복잡한 것을 구축하는 코드를 작성합니다. 실제 공장처럼 : 주문을하고 완제품을 돌려받습니다.
Aaron Digulla 2012-08-15

18
부작용 때문에 목록 이해력을 사용하고 결과 목록으로 유용한 작업을하지 않는 것이 마음에 들지 않습니다. 루프에 대한 간단한은 두 개의 행 ... 퍼지는 경우에도 깨끗한 것
이오안 알렉산드 루 Cucu

1
@Aaron DIgull 나는 내 말 나라면 것은 난 그냥 할 것이라는 점을 이해 for x in threads: x.join()하지 않고 목록 comprehantion을 사용하는 것보다
이오안 알렉산드 루 Cucu

1
@IoanAlexandruCucu : 더 읽기 쉽고 효율적인 솔루션이 있다면 나는 아직도 궁금하네요 stackoverflow.com/questions/21428602/...
아론 Digulla

29

Python3에서는 Python 3.2부터 동일한 결과를 얻을 수있는 새로운 접근 방식이 있습니다. 저는 개인적으로 전통적인 스레드 생성 / 시작 / 조인 패키지를 선호합니다 concurrent.futures. https://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

장점 중 하나는 최대 동시 작업자를 설정하는 처리량을 제어 할 수 있다는 것입니다.


하지만 스레드 풀의 모든 스레드가 언제 완료되었는지 어떻게 알 수 있습니까?
Prime By Design

1
예에서 볼 수 있듯이 with모든 작업이 완료되면 문 이후의 코드 가 실행됩니다.
Roberto

이것은 작동하지 않습니다. 스레드에서 정말 긴 것을 시도하십시오. 스레드가 완료되기 전에 인쇄 문이 실행됩니다
Pranalee

@Pranalee, 그 코드는 작동하며 출력 줄을 추가하도록 코드를 업데이트했습니다. 모든 스레드가 완료되기 전에 "모든 작업 ..."을 볼 수 없습니다 with.이 경우 문이 의도적으로 작동하는 방식입니다. 어쨌든, 항상 새로운 질문을 열고 코드를 게시하면 귀하의 사건에서 무슨 일이 일어나고 있는지 알아내는 데 도움이 될 수 있습니다.
Roberto

@PrimeByDesign concurrent.futures.wait함수 를 사용할 수 있습니다. 여기 에서 실제 예제를 볼 수 있습니다. 공식 문서 : docs.python.org/3/library/…
Alexander Fortin

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/…
Vinayak

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

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.