본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 7장: Free-threaded Python - GIL 제거의 시작
2026년 1월 28일·프로그래밍·

7장: Free-threaded Python - GIL 제거의 시작

Python 3.13의 free-threaded 모드를 심층 분석합니다. GIL의 역사와 문제점, PEP 703의 설계, free-threaded 빌드의 설치와 실전 멀티스레드 성능을 다룹니다.

16분661자10개 섹션
pythontypescriptperformancedevtoolsconcurrency
공유
python-trends7 / 13
12345678910111213
이전6장: CPython 성능 향상의 메커니즘다음8장: 실험적 JIT 컴파일러의 원리와 가능성

GIL이란 무엇인가

GIL(Global Interpreter Lock)은 CPython의 가장 유명한 제약입니다. GIL은 한 시점에 하나의 스레드만 Python 바이트코드를 실행할 수 있도록 보장하는 뮤텍스(mutex)입니다.

GIL이 존재하는 이유

CPython은 참조 카운팅으로 메모리를 관리합니다. 모든 객체에는 참조 횟수를 추적하는 카운터가 있으며, 이 카운터가 0이 되면 객체가 해제됩니다. 문제는 이 카운터가 스레드 안전하지 않다는 것입니다.

GIL 없이 참조 카운팅의 문제
text
스레드 A                     스레드 B
-----------                  -----------
obj.refcount = 1             obj.refcount = 1
                             
x = obj  (refcount++ -> 2)   y = obj  (refcount++ -> ???)
                             
# 두 스레드가 동시에 refcount를 증가시키면
# 2가 아닌 다른 값이 될 수 있음 (경쟁 조건)

GIL은 이 문제를 가장 단순한 방법으로 해결합니다. 한 번에 하나의 스레드만 실행하면 경쟁 조건이 발생하지 않습니다. 이 설계 덕분에 CPython의 C 확장 작성이 단순해지고, 단일 스레드 성능이 최적화되었습니다.

GIL의 대가

GIL의 대가는 명확합니다. CPU 바운드 작업에서 멀티스레딩이 무의미합니다.

GIL의 영향
python
import threading
import time
 
def cpu_bound(n: int) -> int:
    """CPU 집약적 작업"""
    total = 0
    for i in range(n):
        total += i * i
    return total
 
N = 50_000_000
 
# 순차 실행
start = time.time()
cpu_bound(N)
cpu_bound(N)
sequential_time = time.time() - start
 
# 멀티스레드 실행 (GIL 있음)
start = time.time()
t1 = threading.Thread(target=cpu_bound, args=(N,))
t2 = threading.Thread(target=cpu_bound, args=(N,))
t1.start()
t2.start()
t1.join()
t2.join()
threaded_time = time.time() - start
 
print("Sequential: " + str(round(sequential_time, 2)) + "s")
print("Threaded:   " + str(round(threaded_time, 2)) + "s")
# GIL이 있으면 threaded_time >= sequential_time

GIL이 있는 상태에서는 두 스레드가 번갈아가며 실행되므로, 멀티스레드 버전이 순차 실행보다 오히려 느릴 수 있습니다. 컨텍스트 스위칭 오버헤드까지 더해지기 때문입니다.

PEP 703: GIL을 선택적으로

PEP 703은 Sam Gross가 제안한 것으로, CPython에서 GIL을 선택적으로 비활성화할 수 있도록 합니다. Python 3.13에서 실험적으로 도입되었습니다.

설계 원칙

PEP 703의 핵심 설계 원칙은 다음과 같습니다.

PEP 703 설계 원칙
text
1. 기존 코드 호환성 유지
   - GIL 활성 빌드가 기본
   - free-threaded 빌드는 별도 실행 파일
 
2. 단일 스레드 성능 최소 저하
   - 목표: 5% 이내 오버헤드
   - 실측: 1~8% (플랫폼에 따라 다름)
 
3. 점진적 전환
   - C 확장 라이브러리의 호환성 경로 제공
   - 라이브러리가 준비되면 free-threaded 지원 선언

GIL 없이 참조 카운팅을 어떻게 안전하게

PEP 703은 GIL 없이 참조 카운팅을 안전하게 만들기 위해 여러 기법을 도입합니다.

PEP 703의 기술적 해결책
text
1. Biased Reference Counting
   - 객체를 생성한 스레드(소유 스레드)는 빠른 경로 사용
   - 다른 스레드는 원자적 연산으로 참조 카운팅
   - 대부분의 객체는 생성 스레드에서 사용되므로 오버헤드 최소
 
2. Deferred Reference Counting
   - 지역 변수 같은 단기 참조는 카운팅을 지연
   - 가비지 컬렉션 시점에 일괄 처리
 
3. Immortal Objects (PEP 683)
   - None, True, False 등은 참조 카운팅 자체를 하지 않음
   - 6장에서 다룬 내용
 
4. Per-object Locks
   - 객체별 세밀한 잠금으로 동시 접근 제어
   - dict, list 등 컨테이너 객체에 적용

Free-threaded Python 설치와 사용

설치 방법

Free-threaded Python은 별도의 빌드입니다. 공식 설치 프로그램에서 옵션으로 선택하거나, 소스에서 빌드할 수 있습니다.

macOS에서 설치
bash
# 공식 설치 프로그램 사용 시 "Free-threaded" 옵션 체크
# 설치 후 별도 실행 파일 생성
python3.13t  # free-threaded 빌드
 
# 또는 소스에서 빌드
./configure --disable-gil
make
make install
uv로 설치
bash
# uv를 사용하면 더 간편함
uv python install 3.13t
uv run --python 3.13t script.py
Windows에서 설치
bash
# 공식 설치 프로그램에서 "Customize installation" 선택
# "Free-threaded binaries" 옵션 체크
# 설치 후: python3.13t.exe

실행 확인

free-threaded 모드 확인
python
import sys
 
# GIL 상태 확인
print(sys._is_gil_enabled())
# False -> free-threaded 모드
# True  -> GIL 활성 모드
Warning

free-threaded 빌드는 실험적 기능입니다. 프로덕션 환경에서의 사용은 아직 권장되지 않습니다. 일부 C 확장 라이브러리가 호환되지 않을 수 있으며, 예상치 못한 버그가 발생할 수 있습니다.

실전 성능 비교

Free-threaded 빌드에서 실제 멀티스레드 성능이 어떻게 변하는지 테스트합니다.

CPU 바운드 벤치마크

CPU 바운드 멀티스레드 테스트
python
import threading
import time
import sys
 
def compute_sum(start: int, end: int, results: list, index: int) -> None:
    """지정된 범위의 제곱합을 계산"""
    total = 0
    for i in range(start, end):
        total += i * i
    results[index] = total
 
def run_sequential(n: int, num_chunks: int) -> float:
    chunk_size = n // num_chunks
    results = [0] * num_chunks
    start_time = time.time()
    for i in range(num_chunks):
        start = i * chunk_size
        end = start + chunk_size
        compute_sum(start, end, results, i)
    return time.time() - start_time
 
def run_threaded(n: int, num_threads: int) -> float:
    chunk_size = n // num_threads
    results = [0] * num_threads
    threads = []
    start_time = time.time()
    for i in range(num_threads):
        start = i * chunk_size
        end = start + chunk_size
        t = threading.Thread(
            target=compute_sum, args=(start, end, results, i)
        )
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    return time.time() - start_time
 
N = 50_000_000
NUM_THREADS = 4
 
seq_time = run_sequential(N, NUM_THREADS)
thr_time = run_threaded(N, NUM_THREADS)
 
print("GIL enabled: " + str(sys._is_gil_enabled()))
print("Sequential: " + str(round(seq_time, 3)) + "s")
print("Threaded (" + str(NUM_THREADS) + "): " + str(round(thr_time, 3)) + "s")
print("Speedup: " + str(round(seq_time / thr_time, 2)) + "x")
예상 결과
text
# GIL 활성 빌드 (python3.13)
GIL enabled: True
Sequential: 4.521s
Threaded (4): 4.892s
Speedup: 0.92x  # GIL로 인해 오히려 느림
 
# Free-threaded 빌드 (python3.13t)
GIL enabled: False
Sequential: 4.873s  # 단일 스레드에서 약간의 오버헤드
Threaded (4): 1.384s
Speedup: 3.52x  # 거의 4배 가까운 병렬화

I/O 바운드는 어떤가

I/O 바운드 작업에서는 GIL이 I/O 대기 중에 해제되므로, 기존에도 멀티스레딩의 효과가 있었습니다. Free-threaded 모드는 CPU 바운드 작업에서의 변화가 핵심입니다.

작업 유형별 영향
text
CPU 바운드 작업:
  GIL 빌드:    멀티스레딩 효과 없음
  Free-threaded: 코어 수에 비례하는 속도 향상
 
I/O 바운드 작업:
  GIL 빌드:    멀티스레딩 효과 있음 (GIL이 I/O 중 해제)
  Free-threaded: 동일한 효과 + 약간의 추가 개선
 
혼합 작업:
  GIL 빌드:    I/O 부분만 병렬화
  Free-threaded: CPU + I/O 모두 병렬화

라이브러리 호환성

Free-threaded Python의 가장 큰 도전은 생태계 호환성입니다.

C 확장 라이브러리

GIL에 의존하여 작성된 C 확장은 free-threaded 모드에서 안전하지 않을 수 있습니다. 주요 라이브러리의 지원 현황은 다음과 같습니다.

주요 라이브러리 호환성 (2026년 기준)
text
완전 지원:
  - NumPy 2.1+
  - Cython 3.1+
  - pydantic 2.x
 
부분 지원 / 진행 중:
  - pandas
  - scikit-learn
  - matplotlib
 
미지원:
  - 일부 소규모 C 확장 라이브러리

호환성 확인 방법

C 확장 호환성 확인
python
import importlib.metadata
 
def check_free_threading_support(package_name: str) -> None:
    """패키지의 free-threading 지원 여부 확인"""
    try:
        meta = importlib.metadata.metadata(package_name)
        classifiers = meta.get_all("Classifier") or []
        for c in classifiers:
            if "Free-Threading" in c or "free-threading" in c:
                print(package_name + ": free-threading supported")
                return
        print(package_name + ": no free-threading classifier found")
    except importlib.metadata.PackageNotFoundError:
        print(package_name + ": not installed")

스레드 안전한 코드 작성 가이드

Free-threaded 모드에서 올바른 코드를 작성하려면, 스레드 안전성에 대한 이해가 필요합니다.

공유 상태 보호

스레드 안전한 코드
python
import threading
 
# 안전하지 않음: 공유 리스트에 동시 접근
shared_list = []
 
def unsafe_append(items: list[int]) -> None:
    for item in items:
        shared_list.append(item)  # 경쟁 조건 가능
 
# 안전함: Lock 사용
lock = threading.Lock()
 
def safe_append(items: list[int]) -> None:
    for item in items:
        with lock:
            shared_list.append(item)

threading 모듈의 동기화 프리미티브

동기화 도구
python
import threading
 
# Lock: 기본적인 상호 배제
lock = threading.Lock()
 
# RLock: 재진입 가능 Lock (같은 스레드가 여러 번 획득 가능)
rlock = threading.RLock()
 
# Semaphore: 동시 접근 수 제한
semaphore = threading.Semaphore(value=3)  # 최대 3개 스레드 동시 접근
 
# Event: 스레드 간 신호 전달
event = threading.Event()
 
# Barrier: 여러 스레드가 동시에 특정 지점에 도달할 때까지 대기
barrier = threading.Barrier(parties=4)
Tip

free-threaded 모드에서도 GIL 시절의 threading 모듈과 동기화 프리미티브를 동일하게 사용합니다. 차이점은 GIL이 암묵적으로 제공하던 보호가 사라지므로, 공유 상태에 대한 명시적 동기화가 더 중요해진다는 것입니다.

Free-threaded vs multiprocessing

기존에 GIL을 우회하기 위해 사용하던 multiprocessing과 비교합니다.

Free-threaded vs multiprocessing
text
Free-threaded (threading):
  장점:
  - 메모리 공유 (프로세스 간 직렬화 불필요)
  - 프로세스 생성 오버헤드 없음
  - 공유 데이터 구조에 대한 빠른 접근
  단점:
  - 스레드 안전성을 직접 관리해야 함
  - 실험적 기능 (Python 3.13)
 
multiprocessing:
  장점:
  - 완전한 격리 (한 프로세스 충돌이 다른 프로세스에 영향 없음)
  - 오래되고 안정적인 API
  단점:
  - 프로세스 간 데이터 전송 비용 (직렬화/역직렬화)
  - 높은 메모리 사용량 (각 프로세스가 독립적 메모리 공간)
  - 프로세스 생성 오버헤드

향후 전망

Free-threaded Python은 장기적인 프로젝트입니다.

예상 타임라인
text
Python 3.13 (2024) - 실험적 도입, 별도 빌드
Python 3.14 (2025) - 안정화, 더 많은 라이브러리 지원
Python 3.15 (2026) - 기본 빌드에 포함 가능성 검토
Python 3.16+ (2027~) - GIL 없는 빌드가 기본이 될 가능성

Python Steering Council은 free-threaded 모드가 충분히 안정화되고, 주요 생태계가 호환성을 확보하면 기본으로 전환하겠다는 입장입니다.

정리

Free-threaded Python은 30년간 Python의 근본적 제약이었던 GIL을 제거하는 역사적 변화입니다.

  • PEP 703은 biased reference counting과 deferred reference counting으로 GIL 없이 참조 카운팅을 안전하게 만듭니다
  • Python 3.13에서 실험적으로 도입되었으며, 별도 빌드(python3.13t)로 사용합니다
  • CPU 바운드 멀티스레드 작업에서 코어 수에 비례하는 성능 향상이 가능합니다
  • 단일 스레드 성능에서 1~8%의 오버헤드가 있습니다
  • C 확장 라이브러리의 호환성 확보가 핵심 과제입니다

다음 장 미리보기

8장에서는 Python 3.13의 또 다른 실험적 기능인 JIT 컴파일러를 다룹니다. copy-and-patch 컴파일 기법의 원리, JIT 빌드 방법, 그리고 현재 성능과 미래 가능성을 분석합니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#python#typescript#performance#devtools#concurrency

관련 글

프로그래밍

8장: 실험적 JIT 컴파일러의 원리와 가능성

Python 3.13에 도입된 실험적 JIT 컴파일러를 분석합니다. copy-and-patch 기법의 원리, Tier 2 최적화 파이프라인, 빌드와 활성화 방법, 성능 벤치마크를 다룹니다.

2026년 1월 30일·17분
프로그래밍

6장: CPython 성능 향상의 메커니즘

Python 3.12의 성능 향상 원리를 분석합니다. 특수화 적응 인터프리터, 컴프리헨션 인라인화, immortal objects, asyncio 최적화 등 CPython 내부를 다룹니다.

2026년 1월 26일·16분
프로그래밍

9장: uv - 차세대 Python 패키지 매니저

Rust로 작성된 차세대 Python 패키지 매니저 uv를 다룹니다. 설치, 프로젝트 관리, 가상 환경, Python 버전 관리, 스크립트 실행까지 실전 워크플로우를 안내합니다.

2026년 2월 1일·14분
이전 글6장: CPython 성능 향상의 메커니즘
다음 글8장: 실험적 JIT 컴파일러의 원리와 가능성

댓글

목차

약 16분 남음
  • GIL이란 무엇인가
    • GIL이 존재하는 이유
    • GIL의 대가
  • PEP 703: GIL을 선택적으로
    • 설계 원칙
    • GIL 없이 참조 카운팅을 어떻게 안전하게
  • Free-threaded Python 설치와 사용
    • 설치 방법
    • 실행 확인
  • 실전 성능 비교
    • CPU 바운드 벤치마크
    • I/O 바운드는 어떤가
  • 라이브러리 호환성
    • C 확장 라이브러리
    • 호환성 확인 방법
  • 스레드 안전한 코드 작성 가이드
    • 공유 상태 보호
    • threading 모듈의 동기화 프리미티브
  • Free-threaded vs multiprocessing
  • 향후 전망
  • 정리
  • 다음 장 미리보기