Jupyter 기반 환경에서 Multiprocess 실행시 print 출력 차이.

jupyter notebook에서 실행하면 차일드 프로세스로 실행한 print() 로 출력하는 결과를 확인할 수 업다. print 는 std out 에 출력하도록 되어 있다. 자식 프로세스에서 print 를 호출하면 spawn 을 사용해서 호출되어서 출력되지 않는다.

차일드 프로세스 print 문제

아래 코드는 아주 간단하게 메인 프로세스에서 새 프로세스를 생성하고 실행한다.

1
2
3
4
5
6
7
8
9
from multiprocessing import Process

def func(msg):
print(msg)

if __name__ == "__main__":
proc = Process(target=func, args=('Hello multiproess',))
proc.start()
proc.join()

이 코드는 소스로 쉘에서 실행하거나 Web 기반의 jupyterlab 에서 실행하면 아무 문제이 print 문이 잘 작동하고 출력 결과가 나온다.

1
2
>>> ! python mutiprocess1.py
Hello multiproess

print 의 Multiprocessing 에서 문제는 링크 참고1 을 읽어보기를 권한다. 이 글에서는 테스트한 내용을 정리만 했다.

원인

아래 링크 참고1 기사에서 설명을 자세히 하고 있다. 보통 파이썬에서 차일드 프로세스를 시작할 때는 'fork', 'spawn', 'forkserver' 방법이 있다고 한다. 그런데 spawn 으로 차일드 프로세스를 실행하면 std io 출력 버퍼가 자동으로 비워지지 않는다(print는 기본으로 flush가 되지 않는다). 그러다 보니 차일드 프로세스가 종료 하면서 자동으로 gabage collection에 의해 버퍼에 남아 있는 메시지가 사라지는 것이다.

해결 방법

  1. 메시지 버퍼가 사라지기 전에 flush 를 한다.
  2. fork 기반의 프로세스를 생성한다.

1. flush 사용

  1. 메시지 버퍼가 사라지기 전에 flush 를 한다.

이를 해결하기 위해 모든 print 의 출력은 즉시 flush 되도록 flush=True 옵션을 사용할 수 있다고 한다.

1
2
def func(msg):
print(msg, flush=Yes)

이 방법은 VS code 현재 jupyter 확장에서는 효과가 없다.

flush 란?

std io 의 버퍼 처리 흐름은 글 파일 I/O 버퍼링 을 살펴보고,

python에서 print 같은 표준 출력의 flush에 대해서 이글 의 그림을 참고한다.

2. fork 기반의 프로세스를 생성한다.

지원되는 프로세스 시작 방법은 아래 같이 확인이 가능하다.

1
2
3
4
import multiprocessing as mp

mp.get_start_method() # 현재 start 메서드
mp.get_all_start_methods() # 모든 start 메서드

아래 같이 start method 를 fork 로 강제 사용할 수 있다.

1
2
3
import multiprocessing as mp

mp.set_start_method('fork') # 현재 start 메서드

브라우저 기반의 jupyter 실행환경은 'fork', 'spawn', 'forkserver' 를 모두 가능하다. 하지만 현재 VS code 의 jupyter 확장은 에서는 spawn 만 지원하고 있다.

fork, spawn

링크 침고2 에 있는 글에서 Fork, Spawn 에 대한 설명을 읽어보자.

Forking

  • 포크한 부모 프로세스의 이미지를 그래도 사용한다.

Spwaning

  • 포크한 부모 프로세스와 다르게 새 이미지로 갱신한다.

sleep 으로 지연하면?

VS code 환경에서 아래 같이 sleep 으로 지연을 주어 봤지만 해결이 안된다.

1
2
3
4
5
6
7
8
def func(msg):
print(msg, flush=True)
time.sleep(1)

if __name__ == "__main__":
proc = mp.Process(target=func, args=('Hello multiproess',))
proc.start()
proc.join()

아래 같이 join 전에 sleep 으로 지연

1
2
3
4
5
6
7
8
9
def func(msg):
print(msg, flush=True)
time.sleep(1)

if __name__ == "__main__":
proc = mp.Process(target=func, args=('Hello multiproess',))
proc.start()
time.sleep(1)
proc.join()

결론

현재 테스트한 VS Code 의 jupyter 확장 버전은 아래 같다.

  • Windows VS code, Jupyter Extension
    • v2023.6.1101941928
    • v2023.7.1001901100

만약 VS code 에서 Multiprocess 디버깅시 다른 logger 라이브러리 등을 이용해야 할 것 같다.


참고

참고1 : How to print() from a Child Process in Python

참고2 : fork, vfork 그리고 posix_spawn 이야기

참고3 : foring, spawning 이미지