Computer >> 컴퓨터 >  >> 프로그래밍 >> Python

Python GIL(전역 인터프리터 잠금)이란? 개념과 작동 원리 완벽 정리

이 글에서는 Python의 전역 인터프리터 잠금(Global Interpreter Lock, 이하 GIL)이 무엇인지, 그리고 왜 존재하는지 자세히 알아보겠습니다.

GIL은 여러 스레드가 동시에 Python 인터프리터에 접근해 바이트코드를 실행하지 못하도록 막는 일종의 잠금(Lock) 메커니즘입니다. 즉, 한 시점에 오직 하나의 스레드만 Python 코드를 실행할 수 있습니다. 이로 인해 멀티스레드 아키텍처에서 진정한 멀티스레딩이 불가능하다는 점이 Python 3.x 이전 버전부터 지속적으로 지적되어 온 문제이기도 합니다.

GIL은 왜 도입되었을까?

Python은 자동 가비지 컬렉션(Automatic Garbage Collection)을 지원합니다. 객체의 참조 카운트(reference count)가 0에 도달하는 순간, 해당 객체가 차지하던 메모리는 해제되어 다시 사용할 수 있게 됩니다.

>>> import sys
>>> var = {}
>>> print(sys.getrefcount(var))
2
>>> v = var
>>> print(sys.getrefcount(v))
3

그런데 만약 참조 카운트가 여러 스레드에 의해 동시에 증가하고 감소한다면 어떻게 될까요? 가비지 컬렉터가 올바르게 동작할 수 없어 메모리 누수(memory leak)나 데이터 경합(race condition)이 발생할 위험이 커집니다.

이러한 위험을 줄이기 위해 GIL이 도입되었습니다. 모든 변수마다 개별적인 잠금을 거는 방식도 있지만, 이 경우 수많은 잠금이 연쇄적으로 발생해 교착 상태(deadlock)를 유발하기 쉽습니다. 따라서 인터프리터 전체에 하나의 전역 잠금을 거는 것이 더 단순하고 효율적인 선택이었던 것입니다.

왜 GIL은 아직도 제거되지 않았을까?

GIL을 없애면 멀티스레딩 성능 문제가 해결될 것 같지만, 실제로는 그렇게 간단하지 않습니다. Python의 핵심 구현체인 CPython은 C 언어 기반으로 작성되어 있고, 내부적으로 참조 카운팅 방식의 메모리 관리에 깊이 의존하고 있기 때문입니다. GIL을 단순히 제거하면 기존에 GIL이 보장해주던 스레드 안전성(thread safety)이 무너지고, 이를 대체하는 구현은 성능 저하나 실행 시간 증가로 이어질 수 있습니다.

그래서 Python 커뮤니티는 GIL을 제거하기보다 개선하는 방향으로 노력해 왔습니다. 실제로 최신 Python 버전에서는 서브 인터프리터(subinterpreter) 지원 강화, 그리고 PEP 703을 통해 논의된 free-threaded(No-GIL) 빌드 실험 등이 진행되고 있습니다.

GIL이 실제로 작동하는 방식

예를 들어 프로세스 P1이 실행 중이고, 그 안에 t1과 t2라는 두 스레드가 있다고 가정해 봅시다. Python의 스레드는 운영체제의 네이티브 스레드이므로, 스케줄링은 운영체제가 담당합니다.

t1 실행(GIL 획득) → t1이 I/O 대기 상태 진입(GIL 해제) → t2 실행(GIL 획득, 이 시점에 t1도 실행 준비가 되었지만 GIL은 이미 t2가 보유 중)

이처럼 GIL은 멀티스레드 환경에서 병목 지점이 됩니다. 따라서 CPU 연산이 많은(CPU-bound) 작업을 수행하는 멀티스레드 Python 애플리케이션이나 모듈을 작성하더라도, 기대했던 성능 향상을 얻기 어렵습니다.

결국 Python에서는 실질적인 멀티스레딩이 불가능하며, 멀티코어 CPU가 장착된 시스템이라 하더라도 한 번에 하나의 코어만 활용하게 됩니다.

그럼에도 GIL이 큰 문제가 되지 않는 경우

다행히 대부분의 Python 애플리케이션(웹 애플리케이션, Django 기반 서버 등)은 본질적으로 I/O 바운드(I/O-bound) 특성을 가집니다. 네트워크 요청, 데이터베이스 조회, 파일 입출력 등 대기 시간이 긴 작업이 많기 때문에, 스레드가 I/O를 기다리는 동안 GIL을 해제하고 다른 스레드에게 실행 기회를 넘겨줍니다. 이런 환경에서는 GIL이 큰 걸림돌이 되지 않습니다.

또한 일반적인 Python 프로그래머는 GIL을 직접 획득하거나 해제하는 코드를 작성할 필요가 없습니다. 다만 Python에서 실행 가능한 C/C++ 확장 모듈을 직접 작성하는 경우에는 GIL을 명시적으로 다뤄야 할 수 있습니다.

마무리

이번 글에서는 Python의 전역 인터프리터 잠금(GIL)이 무엇인지, 왜 도입되었는지, 그리고 왜 쉽게 제거할 수 없는지 살펴보았습니다. CPU-bound 작업에서는 multiprocessing 모듈을 활용하고, I/O-bound 작업에서는 threading이나 asyncio를 활용하는 것이 GIL 환경에서 성능을 극대화하는 핵심 전략입니다.