OS 스레드 프로그래밍

GIL 활성 CPython에서는 I/O 대기 스레드가 더 효과적이다

기본 CPython의 GIL은 한 번에 하나의 스레드만 Python 바이트코드를 실행하게 합니다. 하지만 I/O 대기 중에는 GIL이 풀려 다른 스레드가 진행할 수 있습니다.

CPU 바운드

순수 Python 계산

total += i * i처럼 Python 바이트코드를 계속 실행하면 스레드가 GIL을 번갈아 잡습니다.

순차 실행 2.10s
2스레드 2.35s

I/O 바운드

대기 시간이 긴 작업

time.sleep(), socket.recv()처럼 대기하는 동안 GIL이 풀려 시간이 겹칩니다.

순차 실행 2.00s
2스레드 1.01s

GIL이 보이는 흐름

CPU 작업 계산이 계속 이어짐
Thread A 실행 Python bytecode가 GIL을 점유
Thread B 대기 같은 인터프리터 안에서 실행 차례를 기다림
전환 발생 GIL 소유자가 바뀌지만 동시에 계산하지 않음
비용 누적 경합 contention과 컨텍스트 전환이 더해짐
I/O 작업 대기 중 진행 기회가 생김
Thread A 대기 sleep 또는 네트워크 I/O에서 GIL 해제
Thread B 실행 A가 기다리는 동안 다른 작업을 진행
둘 다 I/O 대기 OS가 완료 이벤트를 기다림
결과 수집 join()으로 완료를 합류

무엇을 선택할까

CPU 바운드

multiprocessing이나 ProcessPoolExecutor로 여러 코어를 사용합니다.

I/O 바운드

threading, ThreadPoolExecutor가 단순하고 효과적입니다.

매우 높은 동시성

연결 수가 많고 작업이 짧다면 async/await도 좋은 선택입니다.