그때그때 CS 정리

GIL이 뭐고, 어떻게 해결할 수 있는지

필만이 2024. 11. 22. 00:20

GIL(Global Interpreter Lock)을 파이썬에 도입한 이유

GIL(Global Interpreter Lock)CPython(파이썬의 가장 널리 사용되는 구현체)에서 도입된 메커니즘으로, 여러 스레드가 동시에 실행될 때, 한 번에 하나의 스레드만 Python 바이트코드를 실행할 수 있도록 제한합니다. 이는 다중 스레드 환경에서 데이터 안정성과 성능을 유지하기 위해 고안된 것입니다.


1. GIL의 도입 이유

(1) 메모리 관리의 간단화

  • CPython의 메모리 관리 구조:
    • CPython은 참조 카운트(reference counting) 기반으로 객체의 메모리를 관리합니다.
    • 참조 카운트는 각 객체가 몇 개의 변수에서 참조되는지 기록하고, 참조 카운트가 0이 되면 메모리를 해제합니다.
  • 문제:
    • 여러 스레드가 동시에 객체의 참조 카운트를 변경하면, 경쟁 상태(race condition)가 발생할 수 있습니다.
    • 이를 방지하려면 참조 카운트 연산에 대한 락(lock)이 필요합니다.
  • GIL의 역할:
    • GIL은 한 번에 하나의 스레드만 Python 바이트코드를 실행하도록 제한하여, 참조 카운트 연산이 항상 안전하게 처리되도록 합니다.
    • 별도의 락 없이 메모리 관리를 단순화합니다.

(2) 구현의 단순화

  • CPython은 기본적으로 C로 작성된 인터프리터입니다.
  • 다중 스레드 환경에서의 동시성 처리(예: 락, 세마포어 등)를 구현하는 것은 코드 복잡도를 크게 증가시킵니다.
  • GIL로 통합된 락 관리:
    • GIL은 스레드 간의 동시 실행 문제를 단일 락으로 단순화하여, 인터프리터 구현을 간단하게 유지합니다.

(3) 성능 최적화 (단일 스레드 환경에서)

  • 단일 스레드 프로그램에서는 GIL이 별다른 성능 저하를 일으키지 않습니다.
  • 멀티 스레드 대신 단일 스레드 프로그램에서의 성능 최적화에 집중한 결정이었습니다.
  • 과거에는 멀티코어 시스템이 일반적이지 않았으므로, GIL은 설계 당시 성능 타협으로 받아들여졌습니다.

(4) 외부 C 확장 모듈의 호환성

  • CPython은 외부 C 확장 모듈과 긴밀히 통합되어 있으며, 많은 C 확장 모듈은 멀티스레드 환경에서의 안전성을 보장하지 못합니다.
  • GIL은 이러한 C 확장 모듈이 스레드 안전성을 걱정하지 않고도 동작할 수 있도록 보장합니다.

2. GIL의 장점

  1. 코드 간소화:

    • GIL 덕분에 CPython의 메모리 관리와 스레드 동기화가 간단해짐.
    • 개발자가 복잡한 동기화 문제를 직접 처리할 필요가 없음.
  2. 안정성 보장:

    • GIL은 Python 스레드가 경쟁 상태(race condition) 없이 실행되도록 보장.
    • 멀티스레드 환경에서도 데이터 손상을 방지.
  3. C 확장 모듈 지원:

    • GIL은 C 기반 확장 모듈이 Python의 멀티스레드 환경에서 안정적으로 동작할 수 있도록 돕습니다.

3. GIL의 단점

  1. 멀티코어 CPU의 활용 제한:

    • GIL로 인해 Python 멀티스레드 프로그램은 멀티코어 CPU에서 병렬 실행이 불가능.
    • 하나의 코어만 사용하며, 나머지 코어는 유휴 상태가 됨.
  2. 멀티스레드 프로그램 성능 저하:

    • GIL은 스레드가 자주 전환될 때 오버헤드를 초래.
    • CPU 바운드 작업에서는 멀티스레드의 성능 이점을 잃게 됨.
  3. I/O 바운드 작업에서의 제약:

    • GIL이 멀티스레드 I/O 작업의 성능에 영향을 미치지는 않지만, 병렬성이 제한되어 다른 구현체보다 느릴 수 있음.

4. GIL의 대안과 개선 노력

(1) 멀티프로세싱(multiprocessing) 모듈

  • Python은 GIL의 한계를 극복하기 위해 멀티프로세싱을 제공합니다.
  • 프로세스는 GIL의 영향을 받지 않으므로, 멀티코어 환경에서 병렬 처리가 가능합니다.
from multiprocessing import Pool

def square(x):
    return x * x

if __name__ == "__main__":
    with Pool(4) as p:
        print(p.map(square, [1, 2, 3, 4]))

(2) GIL을 제거한 구현체

  • PyPy:
    • Python의 또 다른 구현체로, GIL 없이 동작.
pypy3 multithread_example.py
  • Jython, IronPython:
    • 각각 Java와 .NET 기반 구현체로, GIL 없이 멀티스레드 환경을 지원.
jython multithread_example.py
ipy multithread_example.py

5. 결과 요약

구현체 GIL 존재 병렬 실행 JIT 사용 비고
CPython 있음 불가능 없음 안정적, 널리 사용됨, 멀티코어 활용 제한.
PyPy 있음 제한적 있음 JIT으로 단일 스레드 성능은 뛰어남.
Jython 없음 가능 없음 Java 환경에서 멀티코어 활용 가능.
IronPython 없음 가능 없음 .NET 플랫폼에서 병렬 실행 가능.

6. 결론

  • GIL은 Python(CPython)의 단순한 메모리 관리와 안정성을 유지하기 위한 타협으로 도입된 메커니즘입니다.
  • 멀티코어 환경이 보편화된 현재에서는 단점이 두드러지지만, I/O 바운드 작업이나 단일 스레드 프로그램에서는 여전히 적합한 성능을 제공합니다.
  • GIL의 한계를 극복하려면 멀티프로세싱, 비동기 I/O, 또는 GIL이 없는 Python 구현체(PyPy, Jython 등)를 사용하는 방법을 고려해야 합니다.