GIL과 병렬프로그래밍1

MiHyun Park·2021년 8월 16일
0

GIL

Python은 인터프리터 언어로 인터프리터 내에서 명령어 한 줄 단위로 컴파일과 실행이 된다. GIL(Global Interpreter Lock)은 하나의 스레드가 인터프리터에서 실행되고 있을 때, 인터프리터에 락을 걸어 다른 스레드가 사용할 수 없도록 막아두는 것이다. 일종의 Mutex(임계 영역)인 셈이다. 따라서 원칙적으로 Python에서는 Multi-Threading이 불가능하다. 스레드를 여러 개 생성해 실행시키더라도 여러 개의 스레드가 병렬적으로(각각 동시에) 실행되는 것이 불가능하기 때문이다.

GIL의 등장배경

Python의 가장 보편적인 구현체는 C언어로 구현된 CPython이다. Python의 대부분의 객체는 포인터 변수로 선언되어 힙 공간에 위치하게 되는데, 이때 객체마다 type과 reference type을 갖는다.

🤔Type은 자료형일테고, Reference Type은 뭘까?

Reference Type은 해당 객체에 대해 다른 객체, 전역 변수, 함수의 로컬 변수 등 다른 위치에서 발생한 참조 횟수로 0의 값을 가지면 객체의 할당이 취소된다. 이를 사용함으로써 변수가 객체를 가리키는 한 객체의 할당 취소가 발생하지 않음을 확신할 수 있고, 제한된 크기의 메모리를 효율적으로 사용할 수 있다는 장점을 갖게 된다.

그렇다면 장점만 있을까? Multi-Thread환경을 생각해보자. 한 프로세스 내 다수의 스레드가 동시에 실행되고 있다면 공유하고 있는 하나의 객체를 동시에 사용하고 있는 상황을 충분히 떠올릴 수 있다.

🤔두 스레드가 동시에 한 객체의 reference type을 증가시킨다면?

위의 경우, 값이 +2되어야하지만 실제로는 +1밖에 되지 않을 것이다. 이처럼 순서나 타이밍, 조작할 수 없는 이벤트가 시스템의 상태에 영향을 미치는 상황을 Race condition(경쟁 상태)라고 한다. Python은 공유하고 있는 데이터로 인해 스케줄링에 의존하게 되고, 이는 스레드들이 경쟁 상태에 있게 되어 Thread-safe하지 않음을 의미한다.

Thread-safe를 보장하지 않는 인터프리터의 문제를 해결하기 위해 채택된 대안이 GIL이다.

GIL의 한계

GIL을 획득한 스레드만이 인터프리터에서 실행될 수 있다. 단 하나만의 스레드가 실행될 수 있기 때문에 병렬성은 갖지 못하지만, 정기적인 스레드 전환으로 동시성은 확보할 수 있다. 또한, I/O 작업에서 GIL이 해제되어 스레드 전환이 일어난다.

즉, CPU를 많이 사용하는(GIL 내부에서 CPython 바이트 코드 해석하는 것이 대부분인) 멀티 스레드 프로그램에서는 스레드 전환의 오버헤드 등으로 병목현상이 일어나게 된다.

유감스럽게도, Thread-safe를 보장할 수 있으면서 파이썬이 내세우는 원칙(특징)에 영향을 미치지 않을 수 있는 GIL의 대안은 아직까지 제시되지 못했다고 한다. 버전 1.5 이후 버전 3.0까지 거의 동일한 코드를 유지하고 있다고 하니...

참고

https://wiki.python.org/moin/GlobalInterpreterLock
https://python.land/python-concurrency/the-python-gil
https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock
https://docs.python.org/3/c-api/intro.html#objects-types-and-reference-counts
http://www.dabeaz.com/python/GIL.pdf

profile
나는 박미현

0개의 댓글