|

Python free-threading은 GIL을 없앤 걸까: 병렬 처리 기대와 현실적인 한계

Python free-threading은 GIL을 없앤 걸까: 병렬 처리 기대와 현실적인 한계
free-threading은 GIL이 사라진 미래라기보다 선택 가능한 실행 모드에 가깝다

Python free-threading은 “드디어 GIL이 없어졌다”는 말로만 이해하면 위험합니다.

정확히는 CPython에서 GIL을 비활성화한 free-threaded build를 사용할 수 있게 되었고, 이 모드가 어떤 코드에서 이득을 주는지 따져봐야 합니다.

그래서 이 글의 핵심 질문은 “GIL이 없어졌나?”가 아니라 “내 Python 코드가 free-threading으로 실제 이득을 볼 조건인가?”입니다.


Python free-threading을 먼저 한 줄로 보기

Python free-threading 핵심 카드
GIL이 선택 가능해져도 병렬 코드를 안전하게 짜는 문제는 남는다

공식 문서 기준으로 free-threaded build는 여러 CPU core에서 Python thread가 병렬로 실행될 수 있게 합니다.

하지만 모든 프로그램이 자동으로 빨라지는 것은 아니고, extension module과 thread-safety가 큰 변수입니다.


GIL은 왜 병렬 처리의 병목으로 불렸을까

GIL은 Global Interpreter Lock의 줄임말입니다. 기존 CPython에서는 여러 thread가 있어도 Python bytecode를 실행하는 순간에는 한 thread만 인터프리터를 잡는 구조였습니다.

이 구조는 메모리 관리와 C extension 생태계에는 단순함을 줬지만, CPU-bound 작업을 여러 thread로 나눠도 기대만큼 빨라지지 않는 이유가 됐습니다.

반대로 네트워크 요청, 파일 입출력처럼 기다리는 시간이 많은 I/O-bound 작업은 GIL만으로 설명되지 않습니다. 이런 작업은 asyncio나 thread pool로도 충분히 개선될 수 있습니다.


free-threaded build는 무엇이 달라질까

Python 공식 문서는 3.13부터 free-threaded Python binary를 선택적으로 지원한다고 설명합니다. 이 빌드에서는 GIL을 비활성화한 실행이 가능하고, thread가 여러 CPU core에서 병렬로 동작할 수 있습니다.

다만 이것은 기본 Python 실행 경험이 모두 한순간에 바뀌었다는 뜻은 아닙니다. 어떤 설치본을 쓰는지, GIL이 실제로 꺼져 있는지, 사용하는 패키지가 준비됐는지 확인해야 합니다.

import sys
import sysconfig

print(sys.version)
print(sysconfig.get_config_var("Py_GIL_DISABLED"))

if hasattr(sys, "_is_gil_enabled"):
    print("GIL enabled:", sys._is_gil_enabled())

공식 문서는 sysconfig.get_config_var("Py_GIL_DISABLED")로 빌드가 free-threading을 지원하는지 확인할 수 있다고 설명합니다.


C extension이 준비되지 않으면 GIL이 다시 켜질 수 있다

Python 생태계의 많은 성능 라이브러리는 C extension에 기대고 있습니다. 공식 문서는 free-threaded build에서 일부 extension module이 아직 준비되지 않았을 수 있고, 이런 모듈을 import하면 GIL이 다시 활성화될 수 있다고 설명합니다.

이 말은 중요한 현실적 의미를 가집니다. 내 코드가 Python thread를 잘 나눴더라도, 핵심 패키지가 free-threading을 지원하지 않으면 기대한 병렬 효과가 줄어들 수 있습니다.

따라서 free-threading 도입을 검토할 때는 Python 버전만 볼 것이 아니라 의존 패키지의 지원 상태까지 봐야 합니다.


thread-safe 코드는 여전히 개발자 책임이다

GIL이 있을 때도 경쟁 상태가 완전히 사라지는 것은 아니었습니다. free-threading에서는 이런 문제가 더 눈에 띄기 쉽습니다.

from threading import Lock, Thread

counter = 0
lock = Lock()

def add():
    global counter
    for _ in range(100_000):
        with lock:
            counter += 1

threads = [Thread(target=add) for _ in range(4)]
for t in threads:
    t.start()
for t in threads:
    t.join()

print(counter)

공유 상태를 수정한다면 Lock, Queue, 불변 데이터 구조처럼 명시적인 동기화 전략을 써야 합니다. free-threading은 병렬 실행의 문을 열지만 안전한 병렬 코드를 대신 작성해주지는 않습니다.


언제 기대해볼 만할까

free-threading의 효과를 기대할 수 있는 쪽은 CPU-bound 작업을 Python thread로 나눌 수 있고, 사용하는 패키지가 free-threading에 준비된 경우입니다.

  • CPU 계산이 많고 작업을 독립적으로 나눌 수 있다
  • 프로세스 간 데이터 복사 비용이 커서 multiprocessing이 부담스럽다
  • 핵심 의존성이 free-threaded build를 지원한다
  • 공유 상태를 작게 만들고 동기화 전략을 세울 수 있다

반대로 대부분의 시간이 데이터베이스, API, 파일 입출력 대기라면 free-threading보다 asyncio, connection pool, 캐시, 쿼리 최적화가 먼저일 수 있습니다.


정리

Python free-threading은 Python 병렬 처리의 중요한 변화입니다. 하지만 “GIL이 사라져서 모든 코드가 빨라진다”가 아니라 “GIL을 비활성화한 빌드가 가능해졌고, 준비된 코드에서 병렬 실행 이점을 볼 수 있다”로 이해하는 편이 정확합니다.

동시성과 병렬성의 차이는 파이썬 asyncio는 언제 필요할까에서 함께 보면 좋습니다. 다른 언어의 동시성 흐름은 Java virtual threads 글과 비교해볼 수 있습니다.

자세한 최신 기준은 Python free-threading 공식 문서PEP 703을 확인하는 것이 안전합니다.

함께보면 좋은 글