홍카나의 공부방

[Python] GIL(Global Interpreter Lock)이 뭘까? 본문

Programming Language/Python

[Python] GIL(Global Interpreter Lock)이 뭘까?

홍문관카페나무 2023. 6. 2. 22:23

https://youtu.be/t6I4Gs_VjGU

 

Global Interpreter Lock(GIL)

  • GIL은 여러 개의 쓰레드가 파이썬 바이트코드를 한 번에 하나만 사용할 수 있도록 Lock을 거는 기법을 의미한다.
  • 즉, 파이썬이 실행될 때는 특정 시점에 오직 하나의 쓰레드만 실행된다는 것이다.
  • 어떻게 보면 OS에서의 쓰레드 개념을 적극적으로 활용할 수 없게 되는 것이다.

병렬이 아닌 "동시적"으로 작동이 되는 것을 확인할 수 있다.

  • 그래서 멀티쓰레드를 의도하여 프로그램을 설계해도, GIL 때문에 한 번에 하나의 쓰레드만 실행하게 된다는 것이다.

 

왜 GIL을 쓰나?

  • 먼저 파이썬의 메모리 관리 방식을 알아본다.
  • 파이썬은 레퍼런스 카운팅 기법을 이용하여 메모리를 관리한다.
  • 레퍼런스 카운팅은 Python에서 생성된 객체가, 특정 객체를 가리키는 참조의 수를 추적하는 Count 변수를 가진다는 것을 의미한다.

레퍼런스 카운팅 기법 예시

  • 즉, 위 그림에서 왼쪽 그림의 경우 I1이 특정 객체를 참조하고 있는 상황이고, 오른쪽은 특정 객체를 아무런 객체가 참조하지 않는 상황인 것이다. 오른쪽 상황에서는 객체가 점유한 메모리가 가비지 컬렉터에 의해 해제된다.
>>> import sys
>>> a = []
>>> b = a
>>> sys.getrefcount(a)
3
  • 위 코드 예시를 살펴보면 a의 레퍼런스 카운트는 3이 나온다. a와 b가 똑같은 빈 리스트를 참조하므로 a가 2가 나온다고 생각할 수 있는데(나도 그랬다.), sys.getrefcount()라는 함수에서도 인자로 a를 전달하여 참조하고 있으므로, a의 레퍼런스 카운트는 3이 된다. 이게 0이 되면 가비지 컬렉터에 의해 메모리에서 정리된다.
  • 만약 멀티 쓰레드 환경에서 두 쓰레드가 동시에 값을 늘리거나 줄이는 상황이 발생하면 Race Condition(여러 프로세스 혹은 쓰레드가 공유 자원에 동시에 접근하여 결과에 영향을 줄 수 있는 상태)이 발생할 수 있다.
  • 그래서 레퍼런스 카운팅에 의해 발생할 수 있는 메모리 안정성 문제를 예방하고자 GIL을 사용한다. 한 자원에 한 쓰레드만 lock을 걸고 접근하게 된다면, race condition이 발생할 일이 없으니까 말이다.
  • 그래서 Python에서 멀티 쓰레드를 사용하면 의도치 않게 실행 시간을 더 늘리는 일이 발생할 수 있다. 특히, CPU Bound인 프로그램을 만든다면 GIL 때문에 싱글 쓰레드보다 성능 측면에서 불리할 수 있다.

 

그렇다면 Python의 멀티 쓰레딩은 그냥 별로인 건가?

  • CPU Bound 프로그램은 확실히 GIL로 인한 병목 현상으로 성능이 떨어지게 되지만, I/O Bound는 그렇지 않다.
  • I/O로 인하여 특정 쓰레드가 자주 sleep상태로 변한다면, 그때 발생하는 Context switching의 Overload를 멀티쓰레드 기법으로 극복할 수 있다.
  • 또한, GIL로 인한 성능 저하를 극복하고 병렬 연산을 하기 위해서는 멀티 프로세싱을 이용할 수 있다. Context switching으로 인한 비용이 증가할 수는 있겠지만, 프로세스가 각자의 메모리를 OS로부터 할당받기 때문에 GIL의 영향은 받지 않기 때문이다. (GIL은 여러 쓰레드의 공동 자원 사용을 제한하는 기법임을 다시 한번 유의하자!)

 

참고

반응형