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 이미지

Windows Terminal에서 miniconda/ananconda Profile 사용

Anaconda / Miniconda 를 설치하고 윈도우즈에서 실행시 바로가기로 사용하는 방법을 정리했다. 모든 Commandline, Powershell 에서 conda 명령을 사용하려면 환경변수 PATH 에 지정을 해야 한다. 이 글에서는 바로가기를 사용하면 PATH 환경변수를 활용하지 않고도 쉽게 사용할 수 있다.

Anaconda/Miniconda 설치 프로그램으로 설치를 하면 바로가기를 만들어 준다. 여기서 힌트를 얻으면 된다.

바로가기 속성을 사용해 파워쉘로 conda 환경을 초기화 하기 위해서는 conda-hook.ps1 실행이 필요하다. 설치한 Anaconda 시작프로그램의 속성을 통해 알 수 있다. 보통 설치된 아래 위치에 있다.

  • C:\Users\USERID\miniconda3\shell\condabin\conda-hook.ps1

PowerShell

윈도우 기본 파워쉘과 Powershell 7 으로 바로가기 설정을 보면, conda-hook.ps1 을 포함해 바로가기 아이콘 속성에 파워쉘을 통해 실행하도록 설정하면 된다.

PowerShell 7

powershell 7 으로 Miniconda3 의 base 가상환경 기반 쉘을 실행한다.

1
"C:\Program Files\PowerShell\7\pwsh.exe" -ExecutionPolicy ByPass -NoExit -Command "& 'C:\Users\USERID\miniconda3\shell\condabin\conda-hook.ps1' ; conda activate 'C:\Users\USERID\miniconda3' "

기본 PowerShell 버전

1
%windir%\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy ByPass -NoExit -Command "& 'C:\Users\USERID\miniconda3\shell\condabin\conda-hook.ps1' ; conda activate 'C:\Users\USERID\miniconda3' "

Command Line

윈도우 Command 로 anaconda / miniconda 를 실행하려면 아래 같이 activate.bat 배치를 실행하면 된다.

1
%windir%\System32\cmd.exe "/K" C:\Users\daddy\miniconda3\Scripts\activate.bat C:\Users\daddy\miniconda3

Windows Terminal 프로파일 설정

윈도우 터미널에 새 프로파일 추가시 랜덤한 GUID 값이 필요하다. 터미널에서 New-Guid 명령으로 발행해 사용한다.

1
2
3
4
5
PS> New-Guid

Guid
----
9f2a1a27-fe9b-4421-ba19-6cea57188b17

PowerShell 7 버전

guid 는 New-Guid 로 발행해서 등록하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
{
"commandline": "C:\\Program Files\\PowerShell\\7\\pwsh.exe -ExecutionPolicy ByPass -NoExit -Command \"& 'C:\\Users\\USERID\\miniconda3\\shell\\condabin\\conda-hook.ps1' ; conda activate 'C:\\Users\\daddy\\miniconda3' \"",
"guid": "{fe2a1a27-9efb-4421-ba19-6cea57188b17}",
"name": "Miniconda3 64bit(ps7)",
"startingDirectory": "%USERPROFILE%"
},
{
"commandline": "C:\\Program Files\\PowerShell\\7\\pwsh.exe -ExecutionPolicy ByPass -NoExit -Command \"& 'C:\\Users\\USERID\\anaconda3\\shell\\condabin\\conda-hook.ps1' ; conda activate 'C:\\Users\\daddy\\anaconda3' \"",
"guid": "{3f2k7199-d35c-44e5-b82d-7cd5442175fc}",
"name": "Anaconda 64bit(ps7)",
"startingDirectory": "%USERPROFILE%"
},

윈도우 기본 PowerShell 버전

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"commandline": "%windir%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -ExecutionPolicy ByPass -NoExit -Command \"& 'C:\\Users\\USERID\\miniconda3\\shell\\condabin\\conda-hook.ps1' ; conda activate 'C:\\Users\\daddy\\miniconda3' \"",
"guid": "{573e44d9-1ff5-4f3f-b083-1b2fd9a6a575}",
"name": "Miniconda3 64bit",
"startingDirectory": "%USERPROFILE%"
},
{
"commandline": "%windir%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -ExecutionPolicy ByPass -NoExit -Command \"& 'C:\\Users\\USERID\\anaconda3\\shell\\condabin\\conda-hook.ps1' ; conda activate 'C:\\Users\\daddy\\anaconda3' \"",
"guid": "{f441f482-8cf6-47ed-9a9c-fc40df46c9ac}",
"name": "Anaconda 64bit",
"startingDirectory": "%USERPROFILE%"
},