GIL (Global Interpreter Lock)

About_work·2024년 7월 23일
0

python 기초

목록 보기
64/65
  • Python의 Global Interpreter Lock(GIL, 전역 인터프리터 락): 멀티스레드 환경에서 Python 인터프리터의 실행을 제어하기 위해 사용되는 메커니즘

  • Python의 GIL은 메모리 관리와 데이터 일관성 문제를 해결하기 위해 설계된 메커니즘이지만,
  • 멀티스레드 성능 저하와 같은 단점을 가지고 있습니다.
  • GIL의 영향을 최소화하기 위해 멀티프로세싱을 사용하거나, C 확장 모듈을 사용하는 등의 방법을 고려할 수 있습니다.
  • GIL에 대한 이해는 Python 프로그램의 성능을 최적화하는 데 중요한 역할을 합니다.

GIL의 목적

  • GIL은 주로 다음 두 가지 목적을 가지고 있습니다:
  1. 메모리 관리의 간편화:

    • Python의 메모리 관리 시스템(CPython의 경우)에서 GIL을 사용하면,
    • 여러 스레드가 동시에 메모리를 할당하고 해제하는 것을 방지할 수 있습니다.
    • 이는 메모리 관리의 복잡성을 줄이고, 메모리 안전성을 확보하는 데 도움이 됩니다.
  2. Python 인터프리터의 내부 상태 보호:

    • Python 인터프리터는 많은 전역 변수를 사용하며, 이러한 변수들이 여러 스레드에 의해 동시에 접근되면 데이터 일관성이 깨질 수 있습니다.
    • GIL은 한 번에 하나의 스레드만 Python 바이트코드를 실행하도록 함으로써, 이러한 문제를 방지합니다.

GIL의 동작 방식

  1. 멀티스레드에서의 GIL:

    • 멀티스레드 프로그램에서는 여러 스레드가 번갈아 가며 GIL을 획득하고 Python 코드를 실행
    • GIL을 획득한 스레드는 일정 시간(또는 일정 수의 바이트코드 명령)을 실행한 후 GIL을 해제하고, 다른 스레드가 GIL을 획득하도록 합니다.
    • 이 과정에서 스레드 전환이 이루어지며, 각 스레드는 GIL을 얻기 위해 경쟁하게 됩니다.
  2. I/O-bound와 CPU-bound 작업:

    • I/O-bound 작업:
      • 파일 입출력, 네트워크 통신 등 I/O 작업을 수행하는 동안 스레드는 GIL을 해제할 수 있습니다.
      • 이는 다른 스레드가 GIL을 획득하고 실행될 수 있게 합니다.
    • CPU-bound 작업:
      • 계산 집중적인 작업(CPU-bound 작업)에서는 GIL이 자주 해제되지 않으므로, 멀티스레드 성능이 저하될 수 있습니다.
      • 이 경우, GIL로 인해 멀티스레드의 이점을 제대로 활용하지 못하게 됩니다.

GIL의 장점과 단점

장점

  1. 간편한 메모리 관리:

    • GIL 덕분에 Python의 메모리 관리 시스템이 간단해지고, 메모리 할당 및 해제 시 동기화 문제를 피할 수 있습니다.
  2. 호환성 유지:

    • 많은 C 확장 모듈들이 GIL을 기반으로 작성되었기 때문에, 호환성을 유지할 수 있습니다.

단점

  1. 멀티스레드 성능 저하:

    • CPU-bound 작업에서는 GIL이 병목 현상을 일으켜 멀티스레드 성능이 저하될 수 있습니다. 여러 스레드가 동시에 실행되지 않기 때문에, 멀티코어 CPU의 성능을 제대로 활용하지 못합니다.
  2. 복잡한 동작 모델:

    • GIL로 인해 스레드가 비효율적으로 동작할 수 있으며, 예상치 못한 성능 문제를 일으킬 수 있습니다.

GIL을 우회하는 방법

  1. 멀티프로세싱 (multiprocessing):

    • multiprocessing 모듈을 사용하여 여러 프로세스를 생성하면, 각 프로세스가 별도의 메모리 공간과 GIL을 가지므로 멀티코어 CPU의 성능을 제대로 활용할 수 있습니다.

    • 예시:

      from multiprocessing import Process
      
      def worker():
          print("Worker")
      
      processes = []
      for _ in range(4):
          p = Process(target=worker)
          processes.append(p)
          p.start()
      
      for p in processes:
          p.join()
  2. C 확장 모듈 사용:

    • C로 작성된 확장 모듈을 사용하여 GIL을 일시적으로 해제하고, 멀티코어 성능을 활용할 수 있습니다.

multi threading에서, GIL이 있음에도 메모리 lock에 신경을 많이 써야하는 이유

  • Global Interpreter Lock (GIL)은 단일 인터프리터에서 동시에 하나의 스레드만이 Python 바이트코드를 실행할 수 있도록 합니다.
      • 여러 스레드가 동시에 메모리를 할당하고 해제하는 것을 방지
  • GIL은 메모리 관리를 단순화하고, Python의 내부 상태를 보호하는 데 도움을 줍니다.
  • 그러나 멀티스레딩 환경에서 여전히 메모리 락 (Memory Lock)에 신경을 많이 써야 하는 이유는 다음과 같습니다:

GIL의 한계와 멀티스레딩

  1. GIL은 모든 메모리 접근을 보호하지 않습니다:

    • GIL은 Python 객체의 참조 카운팅을 보호하지만, 모든 메모리 접근을 보호하지는 않음
      • TODO: 객체의 참조 카운팅?
    • 예를 들어, 복잡한 객체 상태 변경이나, C 확장 모듈에서의 메모리 접근은 GIL로 보호되지 않을 수 있음
  2. 비-Python 코드의 동시성 문제:

    • GIL은 순수한 Python 바이트코드의 실행을 제어하지만,
      • C 확장 모듈이나 네이티브 라이브러리는 GIL을 해제하고 실행될 수 있음
    • 이러한 경우, GIL은 더 이상 메모리 접근을 보호하지 않으며, 멀티스레딩 문제(경쟁 조건 등)가 발생할 수 있음
  3. 복합 연산의 원자성 보장 부족:

    • GIL이 있더라도, 복합 연산(complex operation)은 원자적(atomic)으로 실행되지 않을 수 있습니다.
    • 예를 들어, x += 1 같은 단순한 증가 연산도 원자적이지 않으며, 여러 스레드가 동시에 접근하면 예상치 못한 결과를 초래할 수 있습니다.

예제: GIL이 모든 것을 보호하지 않는 경우

아래는 GIL이 있음에도 불구하고 메모리 락을 사용해야 하는 이유를 설명하는 예제입니다:

import threading

# 전역 변수
shared_data = 0

# 데이터 접근을 보호하기 위한 락
lock = threading.Lock()

def increment():
    global shared_data
    for _ in range(1000000):
        with lock:
            shared_data += 1

# 스레드 생성
thread1 = threading.Thread(target=increment)
thread2 = threading.Thread(target=increment)

# 스레드 시작
thread1.start()
thread2.start()

# 메인 스레드가 다른 스레드들이 끝날 때까지 기다림
thread1.join()
thread2.join()

print("Final shared_data:", shared_data)

분석

  1. 전역 변수 shared_data:
    • 이 변수는 여러 스레드가 동시에 접근하고 수정하는 공유 자원
  2. 락 사용 (lock):
    • lock 객체는 스레드 간의 동기화를 위해 사용됩니다.
    • with lock: 구문을 사용하여 shared_data에 대한 접근을 보호합니다.
  3. GIL의 역할:
    • GIL은 단일 인터프리터 내에서 하나의 스레드만이 Python 바이트코드를 실행하도록 보장하지만, shared_data += 1 같은 복합 연산의 원자성을 보장하지 않습니다.
    • 여러 스레드가 동시에 shared_data를 수정하려고 할 때, 예상치 못한 결과가 발생할 수 있습니다.

결론

  • GIL의 제한:

    • GIL은 Python의 내부 메모리 관리를 단순화하고 보호하지만, 모든 동시성 문제를 해결하지는 않습니다.
    • 특히, 복합 연산의 원자성, 비-Python 코드의 동시성 문제 등에서는 GIL이 충분하지 않습니다.
  • 메모리 락의 필요성:

    • 멀티스레딩 환경에서 공유 자원에 대한 안전한 접근을 보장하기 위해서는 메모리 락이 필요
    • 락을 사용하여 데이터 접근을 동기화하면, 스레드 간의 경쟁 조건을 방지하고, 데이터 일관성을 유지할 수 있음
  • Best Practices:

    • 멀티스레딩 프로그램에서 공유 자원을 다룰 때는 항상 락과 같은 동기화 기법을 사용하여 안전한 데이터 접근을 보장해야 합니다.
    • GIL에 의존하지 않고, 명시적으로 메모리 락을 사용하여 스레드 간의 동시성 문제를 해결하는 것이 중요합니다.
profile
새로운 것이 들어오면 이미 있는 것과 충돌을 시도하라.

0개의 댓글