프로그래밍 언어/파이썬

[Python] 파이썬 GIL이란?

eunsour 2023. 2. 14.
반응형

기본 Python 구현

python.org에 배포된 Python 프로그래밍 언어의 정식 구현체이자, 우리가 일반적으로 사용하는 파이썬은 C 언어로 작성된  CPython이다.

 

Python은 인터프리터 프로그래밍 언어이며, Python 코드는 PVM(Python Virtual Machine)이라고 하는 가상 머신에서 컴퓨터가 읽을 수 있는 바이트코드 명령어로 컴파일된다. 우리가 일반적으로 볼 수 있는 .pyc 파일이나 __pycache__ 폴더가 가상 머신에서 해석되는 바이트코드이다.

 

우리는 흔히 "Python의 모든 것은 객체다." 라는 말을 들어보았을 것이다. intstr 과 같은 유형도 객체이다.

CPython의 구현 수준에서는 이는 올바른 정의이며, 이 안에는 다른 모든 객체가 사용하는 PyObject라는 구조체가 있다.

Python에 있는 모든 객체의 할아버지 격인 PyObject 에는 다음 두 가지가 포함된다.

 

  • ob_refcnt : 참조 횟수
  • ob_type : 다른 type에 대한 포인터

 

ob_refcnt garbage collection에 사용되며 object type에 대한 포인터가 있다. 해당 ob_type 은 Python 객체(예: dict 또는 int)를 설명하는 또 다른 구조체이다.

 

각 object에는 해당 object를 저장할 메모리를 가져오는 방법을 알고 있는 고유한 object별 메모리 할당자와 더 이상 필요하지 않은 메모리를 "frees"하는 object별 메모리 할당 해제기가 있다.

 

그러나 메모리 할당 및 해제에 대한 이 모든 이야기에 중요한 요소가 있다. 바로 메모리는 컴퓨터의 공유 리소스이며 서로 다른 두 프로세스가 동시에 같은 위치에 쓰려고 하면 레이스 컨디션이 발생할 수 있다는 점이다.

 

 

GIL(GLOBAL INTERPRETER LOCK)

GIL은 이러한 컴퓨터의 메모리와 같은 공유 리소스를 처리하는 문제에 대한 일반적인 솔루션이다.

Python 위키에서는 GIL을 이렇게 설명한다.

 

In CPython, the global interpreter lock, or GIL, is a mutex that protects access to Python objects, preventing multiple threads from executing Python bytecodes at once. The GIL prevents race conditions and ensures thread safety. In short, this mutex is necessary mainly because CPython's memory management is not thread-safe.

 

요약하자면, GIL은 여러 스레드가 Python 바이트 코드를 동시에 실행하는 것을 방지하는 뮤텍스이며 레이스 컨디션을 방지하고 스레드 안정성을 보장한다고 나와있다.

 

그렇다면 뮤텍스(mutual exclusion)란 무엇일까? 여기에 좋은 예시가 있다.

 

휴대폰이 없던 시절에는 공중 전화를 주로 이용했었다. 거리의 모든 남자들은 각자의 아내에게 전화를 너무나 걸고 싶어 한다.

어떤 한 남자가 처음으로 공중 전화 부스에 들어가서 그의 사랑하는 아내에게 전화를 걸었다면, 그는 꼭 전화 부스의 문을 꼭 잡고 있어야 한다. 왜냐하면 사랑에 눈이 먼 다른 남자들이 전화를 걸기 위해 시도 때도 없이 달려들고 있기 때문이다. 줄 서는 질서 문화 따위는 없다. 심지어 그 문을 놓친다면, 전화 부스에 들이닥친 남자들이 수화기를 뺏어 당신의 아내에게 애정 표현을 할지도 모른다.

아내와의 즐거운 통화를 무사히 마쳤다면, 이제 문을 잡고 있던 손을 놓고 부스 밖으로 나가면 된다. 그러면 공중 전화를 쓰기 위해 달려드는 다른 남자들 중 제일 빠른 한 명이 부스에 들어가서 똑같이 문을 꼭 잡고 그의 아내와 통화할 수 있다.

 

위의 이야기를 아래와 같이 thread 개념에 하나씩 대응시킬 수 있다.

  • thread : 각 남자들
  • mutex : 전화 부스의 문
  • lock : 문을 잡고 있는 남자의 손
  • resource : 공중 전화

 

따라서 동시에 다른 스레드에 의해 수정되어서는 안 되는 코드를 실행해야 하는 스레드(전화를 사용하여 아내와 대화)는 먼저 뮤텍스에 대한 잠금을 획득해야 한다(부스의 문 손잡이를 움켜쥐고 있음). 그래야만 스레드가 해당 코드 줄을 실행할 수 있다(전화 걸기). 그리고 스레드가 해당 코드를 실행하면 다른 스레드가 뮤텍스에 대한 잠금을 획득할 수 있도록 뮤텍스에 대한 잠금을 해제해야 한다(다른 사람이 전화 부스에 액세스할 수 있음).

 

이렇게 공유되는 메모리의 데이터를 여러 스레드가 동시에 사용할 수 없도록 잠그는 일을 mutex가 맡고 있다.

 

 

왜 GIL인가?

하지만 위에서 보았듯이 파이썬의 모든 객체는 참조 횟수(ob_refcnt)를 가진다. 따라서 모든 object마다 mutex를 걸어줘야 하는 비효율이 발생하게 된다. 또한, 반복적인 잠금 및 해제로 인한 성능 저하 및 여러 개의 잠금이 존재하게 되면서 교착 상태가 발생할 수 있다. 그리고 mutex는 프로그래머가 직접 설정을 하는 작업이기 때문에 신뢰성도 저하될 수 있다.

 

GIL은 이러한 교착 상태를 방지하고 성능 오버헤드가 많이 발생하지 않는다. 모든 mutex를 일일이 보호하지 않고 인터프리터 자체를 잠그기로 한 것이다. 이는 모든 CPU 바운드 Python 프로그램을 단일 스레드로 만든다.

 


 

Larry Hastings 의 말에 따르면 GIL의 설계 결정은 Python을 오늘날처럼 인기 있게 만든 요인 중 하나이다.

 

Python은 당시 스레드라는 개념이 없던 시절부터 설계되었으며, 필요로 했던 수많은 기능들이 C extension들로 작성되었다. 이때, 일관성 없는 변경을 방지하기 위해 thread-safe 한 메모리 관리가 필요하였고 당시 구현이 간단했던 GIL이 도입되었다.

 

GIL은 CPython 개발자가 Python의 초기에 직면한 어려운 문제에 대한 실용적인 솔루션이자 단일 스레드의 성능을 끌어낼 수 있던 방법이었다.

 

이렇게 초창기에 만들어진 CPython의 GIL을 제거하려는 다양한 노력들이 생겨나고 있지만, 아직까지 GIL이 제거되지 않은 이유는 간단하다. Python의 창시자이자 BDFLGuido van Rossum은 2007년 9월 "GIL을 제거하기 쉽지 않다"라는 커뮤니티의 답변에

I’d welcome a set of patches into Py3k only if the performance for a single-threaded program (and for a multi-threaded but I/O-bound program) does not decrease.

단일 스레드 프로그램의 성능이 저하되지 않는 경우에만 Python 3.0 버전에 개선안을 받아들이겠다고 하였다. 그리고 이 조건은 이후로도 충족되지 않았다.

 

 

GIL을 우회하는 방법

파이썬 표준 라이브러리의 모든 블로킹 입출력 함수는 OS에서 결과를 기다리는 동안 GIL을 해제해서 다른 스레드가 실행할 수 있게 해 준다. 즉, 입출력 위주의 작업을 실행하는 파이썬 프로그램은 파이썬으로 구현하더라도 스레드를 이용함으로써 이득을 볼 수 있다는 것이다. 파이썬 스레드가 네트워크로부터의 응답을 기다리는 동안, 블로킹된 입출력 함수가 GIL을 해제함으로써 다른 스레드가 실행될 수 있다.

 

따라서 GIL을 사용하고 있더라도 파이썬 스레드는 입출력 위주의 애플리케이션에는 엄청난 효용성이 있다.

 

이외에도 multiprocessing과 같은 병렬 처리 라이브러리를 사용하거나, CPython이 아닌 다른 대체 Python 인터프리터를 사용하는 방법이 있다.

 

 

 

Reference

https://wiki.python.org/moin/GlobalInterpreterLock

https://realpython.com/python-gil/#why-was-the-gil-chosen-as-the-solution

https://realpython.com/python-memory-management/#the-global-interpreter-lock-gil

https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock

https://stackoverflow.com/questions/4989451/mutex-example-tutorial

https://it-eldorado.tistory.com/160

https://dgkim5360.tistory.com/entry/understanding-the-global-interpreter-lock-of-cpython

 

반응형

댓글