본문으로 건너뛰기
개발 머꼬
개발 노트Python
hohyeon.dev16

GIL 없는 파이썬이 정식 지원된다는 말의 실제 의미

  • #Concurrency
  • #Engineering Note
  • #Python

문제 발생

CPU를 쓰는 계산을 스레드로 나눴는데 코어를 하나밖에 못 쓰는 상황이 오래된 골칫거리였습니다. 그래서 multiprocessing으로 프로세스를 띄우고, 프로세스 간에 데이터를 넘기느라 직렬화 비용을 감수해 왔습니다.

from concurrent.futures import ThreadPoolExecutor

with ThreadPoolExecutor(max_workers=8) as pool:
    list(pool.map(cpu_heavy, chunks))   # 코어 8개를 써도 빨라지지 않는다

원인 분석

CPython은 한 번에 하나의 스레드만 파이썬 바이트코드를 실행하도록 GIL(Global Interpreter Lock) 로 제한해 왔습니다. I/O 대기 중에는 GIL을 놓기 때문에 I/O 병렬화에는 도움이 되지만, CPU 계산은 스레드를 늘려도 병렬로 돌지 않습니다.

PEP 703이 GIL을 끌 수 있는 free-threaded 빌드를 제안했고, 3.13에 실험적으로 들어왔습니다. PEP 779로 3.14에서 공식 지원(officially supported) 단계가 됐습니다.

여기서 오해하기 쉬운 지점이 두 개입니다.

첫째, 기본 빌드가 바뀐 게 아닙니다. free-threaded는 별도 빌드(python3.14t)이고, 일반 배포판은 여전히 GIL을 씁니다. "3.14로 올렸으니 이제 GIL이 없다"가 아닙니다.

둘째, 공짜가 아닙니다. 3.14에서 free-threaded 빌드에도 적응형 특수화 인터프리터가 들어가면서 단일 스레드 성능 손해가 약 5~10% 수준으로 줄었지만, 0은 아닙니다. 스레드를 실제로 여러 개 쓰지 않는 워크로드라면 손해만 봅니다.

같은 3.14에는 다른 선택지도 함께 들어왔습니다. PEP 734concurrent.interpreters는 한 프로세스 안에 격리된 인터프리터를 여러 개 두는 방식으로, InterpreterPoolExecutor를 통해 쓸 수 있습니다. 프로세스보다 가볍고 스레드보다 격리됩니다.

해결 방안

  1. 먼저 GIL이 진짜 병목인지 확인합니다. I/O 대기가 대부분이라면 GIL은 원인이 아니고, asyncio나 스레드로 이미 충분합니다. 프로파일러로 CPU 시간과 대기 시간을 나눠 보는 것이 먼저입니다.
  2. free-threaded 빌드는 별도로 설치해 실험합니다. 기존 환경을 바꾸지 않고 비교할 수 있습니다.
python3.14t -c "import sys; print(sys._is_gil_enabled())"
  1. C 확장 모듈을 확인합니다. 이 방식의 실질적인 제약은 언어가 아니라 생태계입니다. 확장 모듈이 free-threaded 빌드를 지원하지 않으면 그 자리에서 막힙니다. 의존성 목록을 먼저 훑어야 합니다.
  2. 중간 선택지를 검토합니다. 프로세스는 무겁고 스레드는 GIL에 묶이는 상황이라면 InterpreterPoolExecutor가 맞을 수 있습니다.
from concurrent.futures import InterpreterPoolExecutor

with InterpreterPoolExecutor(max_workers=8) as pool:
    list(pool.map(cpu_heavy, chunks))
  1. 당장 운영에 넣지 않아도 됩니다. 공식 지원은 "이제 실험이 아니다"라는 뜻이지 "모두가 지금 옮겨야 한다"가 아닙니다. 확장 모듈 생태계가 따라오는 속도가 실제 도입 시점을 정합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.