2019-10-28

파이썬 멀티프로세싱의 .join() 메서드는 무슨 일을 하는걸까?

0. 멀티 프로세싱을 진행할 필요성이 있어 파이썬 예제 코드를 찾아보았고

multiprocessing을 사용하는 코드들은 대개 아래와 같은 구조를 갖고 있음을 발견했다.

from multiprocessing import Process

procs = []

proc = Process(target=sample_func, args=(number,))
procs.append(proc)
proc.start()
proc.join()

근데 마지막에 .join()은 역할이 뭘까?



1. 일단 제일 먼저 하는건 역시나 문서 검색(한글, 영문)

.join([timeout]) 의 아규먼트인 timeout의 값이 None(기본값)인 경우

메서드는 join() 메서드가 호출된 프로세스가 종료될 때까지 block된다.

timeout 값이 양수(positive number)인 경우 최대 해당 시간 동안 block된다.

프로세스가 종료되거나 메서드가 시간초과된 경우엔 None을 돌려준단 점에 주의하라.

프로세스는 여러번 join될 수 있다.

교착 상태를 유발할 수 있으므로 프로세스는 자기 자신을 join할 수 없다.

프로세스가 시작되기 전에 프로세스에 조인하려고 하면 에러가 발생한다.

뭔소리지 이게.. 조인? 메서드?



2. 그래서 더 찾아보았다. 스택오버플로우에 올라온 글을 번역하였다.



질문자의 글 : 파이썬 멀티프로세싱 모듈의 .join() 메서드는 정확히 무슨 일을 하는거죠?

파이썬의 멀티프로세싱에 대해 공부하던 중 join() 메서드가 무슨 일을 하는지 알고 싶어졌다.

2008년의 튜토리얼에 따르면 "아래처럼 p.join()을 넣어주지 않으면

자식 프로세스는 유휴상태(idle)에 들어가고 종료되지 않아 좀비 프로세스가 되어

손수 kill해줘야만 하게 된다"고 한다.

from multiprocessing import Process

def say_hello(name='world'):
    print("Hello, %s" % name)

p = Process(target=say_hello)
p.start()
p.join()

테스트를 위해 time.sleep 동안에 PID를 출력하게 만들었고

프로세스는 알아서 종료되게끔 만들어보았다:

from multiprocessing import Process
import sys
import time

def say_hello(name='world'):
    print "Hello, %s" % name
    print 'Starting:', p.name, p.pid
    sys.stdout.flush()
    print 'Exiting :', p.name, p.pid
    sys.stdout.flush()
    time.sleep(20)

p = Process(target=say_hello)
p.start()
# no p.join()

그랬더니 20초 이내엔 다음과 같은 출력물이:

936 ttys000    0:00.05 /Library/Frameworks/Python.framework/Versions/2.7/Reso
938 ttys000    0:00.00 /Library/Frameworks/Python.framework/Versions/2.7/Reso
947 ttys001    0:00.13 -bash

20초 후엔 아래와 같이 출력됐다:

947 ttys001    0:00.13 -bash



출력 양상은 p.join()을 코드 맨 마지막에 추가했을 때와 같았다.
(p.join이 있던 없던 프로세스는 잘 종료됐다는 뜻)

Python Mole Of The Week엔 멀티프로세싱에 대해 이해하기 쉽게 설명해준 글이 있다.

"프로세스가 작업을 완료하고 종료될 때까지 기다리려면 join() 메서드를 사용하십시오"

하지만 겉보기엔 최소한 OS X가 어찌됐건간에 처리해주는 것 같이 보였다.

또한 이 메서드의 이름에 대해서도 궁금점이 생겼다.

메서드 이름인 .join()은 무언가를 '연결'시킨다는 것을 의미하는 것인가?

이 메서드로 인해 프로세스를 무언가의 끝에 이어주는 것인가? 아니면

이 이름은 파이썬이 가진 내장함수 .join()의 이름을 공유하는 것 뿐인가?



답변 :

join() 메서드는 threading이나 multiprocessing에서 쓰이는 메서드로,

파이썬의 str.join()과는 아무 상관이 없습니다.

이 메서드는 실제로 두 가지 이상의 무언가를 '연결'시켜주는 개념이라기보단

"이 쓰레드/프로세스가 완료될 때까지 기다린다"는 것을 의미합니다.

join이라는 이름이 붙게 된 이유는 multiprocessing 모듈의 API가 뜻하는게

threading 모듈의 API와 비슷하게 보였기 때문입니다.

threading 모듈에서 join은 해당 모듈의 Thread 객체에 사용되는 메서드명이었습니다.

그래서 join이라는 말은 일반적으로 많은 프로그래밍 언어에서

'스레드가 작업을 완료할 때까지 기다림'을 의미하게 되었습니다.

파이썬도 그저 그 관례를 차용했을 뿐입니다.



자 이제 질문자님이 본대로 join()을 호출한 것과 호출하지 않은 것 사이의

20초 딜레이에 대해 이유를 설명해드리죠.

메인 프로세스가 종료될 준비를 끝마쳤을 때(모든 작업이 끝났을 때)

기본적으로 이 프로세스는 암묵적인 룰을 통해

작동하고 있던 multiprocessing.Process 인스턴스에서

join()을 호출할 준비가 되어있습니다.

이 사실이 multiprocessing 문서에 명확하게 적혀있는 내용인 것은 아니지만

Programming Guidelines 섹션에는 다음과 같이 언급이 되어 있습니다.

"Remember also that non-daemonic processes will be automatically be joined"
(비-데몬성 프로세스들이 자동으로 join될 것이란 점도 기억하십시오)

사용자는 'daemon' 플래그를 통해 이 설정을 변경할 수 있습니다.

p = Process(target=say_hello)
p.daemon = Truep.start()
# Both parent and child will exit here, since the main process has completed.

이렇게 하면 자식 프로세스는 메인 프로세스가 완료되자마자 종료될 것입니다.



3. join은 '프로세스가 완료됨을 기다리다'라는 뜻이었다.

아래의 다른 답변에서 이 단어에 대한 어원을 설명한 글도 있어서 함께 끌어왔다.

<

개념은 이렇습니다, 여러개의 프로세스에 "forks"를 수행하는데

그 프로세스들 중 하나는 마스터이고 다른 것들은 노동자들인 셈이죠.

노동자들이 일을 끝마치면 그들은 마스터에게 합쳐(join)집니다.

>

Fork와 join은 멀티프로세싱에 사용되는 개념으로, 따로 읽어보아도 좋다.


댓글 4개:

  1. 좋은 글 감사합니다. 특히 어원을 알게되어서 좋네요.

    답글삭제
  2. 덕분에 잘 이해하고 넘어 갑니다. 좋은 정보 고맙습니다!

    답글삭제
  3. 좋은 글 감사합니다. 찾던 질문에 대한 정확한 답변입니다.
    ps. 검정배경은 눈이 꽤 피로하네요

    답글삭제