프로그래밍 언어/Python

[Python] GIL(Global Interpreter Lock) 정리

benjykim 2021. 2. 28. 07:00
반응형

[Python] GIL(Global Interpreter Lock) 정리

CPython 메모리 관리 방법

  • CPython은 reference의 개수를 세는 방법으로 메모리를 관리한다.

    • sys.getrefcount 라는 함수를 이용해서 Python이 세고 있는 객체의 reference 개수를 확인할 수 있다.

      # 출처: realpython.com
      >>> import sys
      >>> a = []
      >>> b = a
      >>> sys.getrefcount(a)
      3
      • a가 처음 만들어짐 -> reference 1개
      • ba의 reference 할당 -> reference 2개
      • sys.getrefcount 함수에 argument로 a가 들어가서, 이 함수 내부에서 a의 reference 개수를 하나 늘림 -> reference 3개
        (함수 종료 시 reference 개수 3 -> 2개로 바뀜)
    • 따라서 최종 출력으로 나오는 a의 reference 개수는 세 개가 된다. 그리고 이 값이 0이 되면 CPython이 알아서 메모리를 회수한다.


GIL

두 가지를 일단 기억하자

  1. C에서 쓰레드 사용 시, race condition이 일어나지 않도록 하는 것은 사용자의 몫이다.
  2. CPython은 생성되는 개체의 reference를 세어가면서 메모리를 관리한다.

여기서 우리는 CPython이 reference를 세는 과정에서 문제가 생길 수 있음을 생각해볼 수 있다. 만일 reference를 세는 도중에 race condition이 발생한다면, 이는 메모리 유실(memory leak)으로 이어질 것이다. (반대로 아직 살아있어야 할 객체를 죽일수도 있다.)


이를 해결하기 위해서는 뮤텍스(mutex)를 이용하면 된다. 그러나 객체마다 뮤텍스를 넣어주는 것은 성능적으로 많은 손해를 불러 일으킨다. 또한 deadlock이라는 치명적인 위험 상황을 불러들이게 된다.


CPython의 결정은 이렇다. 뮤텍스를 통해 모든 reference 개수를 일일이 보호하지 말고, Python 인터프리터 자체를 잠그기로 했다. 이거 하나만 뮤텍스로 보호하면 우려하던 문제들이 모두 해결된다. (그러나 이말인 즉슨, 많은 쓰레드를 사용해봤자 오직 한 쓰레드만이 파이썬 코드를 실행할 수 있다는 의미이기도 하다.) 사실상 한 프로세스 안에서 여러 쓰레드를 이용한 병렬 처리를 막은 것이라고도 할 수 있다.


정리하자면 GIL은 인터프리터를 잠그는 락(뮤텍스)이고 이를 사용함으로써 race condition 등의 문제를 해결할 수 있다. 대신 한 쓰레드만이 파이썬 코드를 실행할 수 있다.


반응형