Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
 

(시리즈 글이 2개 있습니다.)
스크립트: 68. 파이썬 - multiprocessing Pool의 기본 프로세스 시작 모드(spawn, fork)
; https://www.sysnet.pe.kr/2/0/13874

스크립트: 69. 파이썬 - multiprocessing 패키지의 spawn 모드로 동작하는 uvicorn의 workers
; https://www.sysnet.pe.kr/2/0/13875




파이썬 - multiprocessing 패키지의 spawn 모드로 동작하는 uvicorn의 workers

지난 글에서 multiprocessing 패키지의 Pool 사용을 알아봤는데요,

파이썬 - multiprocessing Pool의 기본 프로세스 시작 모드(spawn, fork)
; https://www.sysnet.pe.kr/2/0/13874

이번에는 uvicorn에서의 multiprocessing 처리를 다뤄보겠습니다. 우선, 테스트를 위해 다음과 같은 main.py 파일을 만들고,

import time
import datetime
import os

import uvicorn
from fastapi import FastAPI

g_var = 0

app = FastAPI()
print(f"main.py with {g_var} at {os.getpid()}")

g_var += 1


@app.get('/')
def home():
    global g_var
    output = f"started = {datetime.datetime.now()}, end = "
    time.sleep(10)
    output += f"{datetime.datetime.now()}, Hello {os.getpid()}, g_var = {g_var}"
    return output


if __name__ == "__main__":
    uvicorn.run("main:app", workers=3, host="0.0.0.0", port=8000, reload=False)

실행하면 workers=3으로 인해 다음과 같은 식으로 출력이 나옵니다.

$ python3 main.py
main.py with 0 at 2585362:139999625058112 - 
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     Started parent process [2585362]
main.py with 0 at 2585367:140270000273216 - <fastapi.applications.FastAPI object at 0x7f93269bdd60>
main.py with 0 at 2585366:139725771581248 - <fastapi.applications.FastAPI object at 0x7f14700d0d60>
main.py with 0 at 2585365:139681518356288 - <fastapi.applications.FastAPI object at 0x7f0a225a6d60>
main.py with 0 at 2585367:140270000273216 - <fastapi.applications.FastAPI object at 0x7f9324cdad30>
INFO:     Started server process [2585367]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
main.py with 0 at 2585366:139725771581248 - <fastapi.applications.FastAPI object at 0x7f146e3edd30>
INFO:     Started server process [2585366]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
main.py with 0 at 2585365:139681518356288 - <fastapi.applications.FastAPI object at 0x7f0a208c3d30>
INFO:     Started server process [2585365]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

잘 보시면, 프로세스 1개의 동일한 스레드에서 FastAPI 개체가 2개씩 생성되는데, 즉, 1개의 프로세스에서 2개의 main.py 인스턴스를 로드하고 있는 것입니다. 또한 이때의 프로세스 구조를 보면,

$ pstree -ap $(pgrep python3 | head -n 1)
python3,2585362 main.py
  ├─python3,2585364 -c from multiprocessing.resource_tracker import main;main(4)
  ├─python3,2585365 -c from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=5, pipe_handle=7) --multiprocessing-fork
  ├─python3,2585366 -c from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=5, pipe_handle=9) --multiprocessing-fork
  └─python3,2585367 -c from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=5, pipe_handle=11) --multiprocessing-fork

uvicorn이 내부적으로 multiprocessing 패키지를 그대로 이용하고 있음을 짐작게 합니다. 재미있는 것은, 리눅스에서도 spawn_main으로 프로세스가 생성되기 때문에 fork 방식이 아니므로 출력에서 g_var 값이 증감 없이 "0"으로 나옵니다. (얼핏 명령행에 "--multiprocessing-fork"라고 돼 있어 fork 방식으로 보일 수 있지만, 실제로는 spawn 방식입니다.)




workers == 3이고, main.py는 각 프로세스 당 2개씩 생성되었는데 이것은 이후 요청의 분배에 따라 상황이 바뀝니다. 그래서 이래저래 요청을 보내다가 어느 순간 프로세스 구조를 보면 이런 식으로 바뀐 것을 볼 수 있습니다.

$ pstree -ap $(pgrep python3 | head -n 1)
python3,2585362 main.py
  ├─python3,2585364 -c from multiprocessing.resource_tracker import main;main(4)
  ├─python3,2585365 -c from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=5, pipe_handle=7) --multiprocessing-fork
  │   ├─{python3},2590355
  │   ├─{python3},2591017
  │   └─{python3},2591018
  ├─python3,2585366 -c from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=5, pipe_handle=9) --multiprocessing-fork
  │   └─{python3},2587767
  └─python3,2585367 -c from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=5, pipe_handle=11) --multiprocessing-fork

출력에도 나오듯이 2585365 프로세스에서 3개의 스레드가 생성됐는데요, 이런 상태에서 클라이언트 3개가 요청을 보내면 모두 2585365 프로세스로 전달돼 처리가 됩니다. (물론, 상황에 따라 달라질 수 있습니다.)

참고로, workers=1로 설정하면 resource_tracker 및 자식 프로세스 없이 원래의 프로세스가 요청 처리를 담당합니다.

// workers를 지정하지 않은 경우, 기본값은 1
// uvicorn.run("main:app", workers=1, host="0.0.0.0", port=8000, reload=False)

$ pstree -ap $(pgrep python3 | head -n 1)
python3,2600344 main.py
  ├─{python3},2600346
  ├─{python3},2600347
  ├─{python3},2600348
  ├─{python3},2600349
  ├─{python3},2600535
  └─{python3},2600566

// uvicorn.run("main:app", workers=2, host="0.0.0.0", port=8000, reload=False)

$ pstree -ap $(pgrep python3 | head -n 1)
python3,2601302 main.py
  ├─python3,2601304 -c from multiprocessing.resource_tracker import main;main(4)
  ├─python3,2601305 -c from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=5, pipe_handle=7) --multiprocessing-fork
  └─python3,2601306 -c from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=5, pipe_handle=9) --multiprocessing-fork




혹시 uvicorn의 자식 프로세스를 spawn이 아닌 fork로 지정할 수 있을까요? 검색 실력이 없어서 그런지 모르겠지만, 일단 제가 찾아본 바로는 그런 옵션이 없습니다. 대신 gunicorn의 경우에는 오히려 기본 방식이 fork라고 합니다.

따라서, fork로 강제하고 싶다면 uvicorn이 아닌 gunicorn으로 호스팅 방식을 바꿔야 할 것입니다. 그러고 보면, 이런 기본 방식의 차이점 때문에 uvicorn은 윈도우에서도 돌아가고, gunicorn은 윈도우 지원을 못 하는 듯합니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 1/24/2025]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 121  122  123  124  125  126  127  128  129  130  131  [132]  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1756정성태9/23/201427486기타: 48. NVidia 제품의 과다한 디스크 사용 [2]
1755정성태9/22/201434280오류 유형: 241. Unity Web Player를 설치해도 여전히 설치하라는 화면이 나오는 경우 [4]
1754정성태9/22/201424641VC++: 80. 내 컴퓨터에서 C++ AMP 코드가 실행이 될까요? [1]
1753정성태9/22/201420610오류 유형: 240. Lync로 세미나 참여 시 소리만 들리지 않는 경우 [1]
1752정성태9/21/201441071Windows: 100. 윈도우 8 - RDP 연결을 이용해 VNC처럼 사용자 로그온 화면을 공유하는 방법 [5]
1751정성태9/20/201438942.NET Framework: 464. 프로세스 간 통신 시 소켓 필요 없이 간단하게 Pipe를 열어 통신하는 방법 [1]파일 다운로드1
1750정성태9/20/201423832.NET Framework: 463. PInvoke 호출을 이용한 비동기 파일 작업파일 다운로드1
1749정성태9/20/201423732.NET Framework: 462. 커널 객체를 위한 null DACL 생성 방법파일 다운로드1
1748정성태9/19/201425384개발 환경 구성: 238. [Synergy] 여러 컴퓨터에서 키보드, 마우스 공유
1747정성태9/19/201428471오류 유형: 239. psexec 실행 오류 - The system cannot find the file specified.
1746정성태9/18/201426105.NET Framework: 461. .NET EXE 파일을 닷넷 프레임워크 버전에 상관없이 실행할 수 있을까요? - 두 번째 이야기 [6]파일 다운로드1
1745정성태9/17/201423035개발 환경 구성: 237. 리눅스 Integration Services 버전 업그레이드 하는 방법 [1]
1744정성태9/17/201431062.NET Framework: 460. GetTickCount / GetTickCount64와 0x7FFE0000 주솟값 [4]파일 다운로드1
1743정성태9/16/201420985오류 유형: 238. 설치 오류 - Failed to get size of pseudo bundle
1742정성태8/27/201426970개발 환경 구성: 236. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 [2]
1741정성태8/26/201421334.NET Framework: 459. GetModuleHandleEx로 알아보는 .NET 메서드의 DLL 모듈 관계파일 다운로드1
1740정성태8/25/201432507.NET Framework: 458. 닷넷 GC가 순환 참조를 해제할 수 있을까요? [2]파일 다운로드1
1739정성태8/24/201426536.NET Framework: 457. 교착상태(Dead-lock) 해결 방법 - Lock Leveling [2]파일 다운로드1
1738정성태8/23/201422046.NET Framework: 456. C# - CAS를 이용한 Lock 래퍼 클래스파일 다운로드1
1737정성태8/20/201419765VS.NET IDE: 93. Visual Studio 2013 동기화 문제
1736정성태8/19/201425576VC++: 79. [부연] CAS Lock 알고리즘은 과연 빠른가? [2]파일 다운로드1
1735정성태8/19/201418198.NET Framework: 455. 닷넷 사용자 정의 예외 클래스의 최소 구현 코드 - 두 번째 이야기
1734정성태8/13/201419858오류 유형: 237. Windows Media Player cannot access the file. The file might be in use, you might not have access to the computer where the file is stored, or your proxy settings might not be correct.
1733정성태8/13/201426362.NET Framework: 454. EmptyWorkingSet Win32 API를 사용하는 C# 예제파일 다운로드1
1732정성태8/13/201434476Windows: 99. INetCache 폴더가 다르게 보이는 이유
1731정성태8/11/201427081개발 환경 구성: 235. 점(.)으로 시작하는 파일명을 탐색기에서 만드는 방법
... 121  122  123  124  125  126  127  128  129  130  131  [132]  133  134  135  ...