GIL 개념
GIL 이란
GIL이란 Global Interpreter Lock의 약자로 파이썬 인터프리터가 한 스레드만 하나의 바이트코드를 실행 시킬 수 있도록 해주는 Lock이다.
쉽게 말해 하나의 스레드에 모든 자원을 허락하고 그 후에는 Lock을 걸어 다른 스레드는 실행할 수 없게 막아버리는 것이다.
일반적인 경우에는 각각의 스레드가 병렬적으로 일할 것이라 생각되지만 이 GIL 때문에 그렇지 않다.
각각의 스레드는 GIL을 얻고 동작하며 이 때 다른 스레드는 모두 동작을 멈추게 된다.
그리고 멀티스레드일 경우 thread context switch에 따른 비용도 발생하기 때문에 오히려 싱글스레드보다 시간이 오래 걸리는 문제가 발생한다.
❓ 왜 GIL을 사용할까
python은 메모리 관리 방법으로 reference counting을 사용한다. 이는 파이썬 오브젝트마다 레퍼런스로 사용된 횟수를 저장하는 것이고, 0이 되는 경우 메모리에서 해제하는 방식이다.
# 빈 배열([])은 a, b, sys.getrefcount에 의해 참조되어 refcount가 3인 것을 확인할 수 있다.
>>> import sys
>>> a = []
>>> b = a
>>> sys.getrefcount(a)
3
레퍼런스 카운트의 문제점은 여러 쓰레드가 동시에 객체의 참조 카운트를 수정할 수 있어 race condition이 발생할 수 있다는 것이다. 그렇기 때문에 refcount를 수정하려고 할 때마다 lock을 걸어야 하는데 모든 객체에 lock을 걸어야 하는 것도 부담이고, 교착상태(deadlock)의 위험성도 있다.
따라서 이 문제를 해결하고자 단 하나의 lock, 인터프리터를 통째로 Lock을 거는 방식을 사용한것이다. 이 방식은 deadlock을 방지할 수 있고 모든 변수에 lock을 거는 것으로 인한 성능 저하는 없어지지만, 모든 cpu 위에서 올라가는 프로그램을 single-threaded로 만든다는 단점이 있다.
파이썬에서 멀티쓰레드 프로그램의 성능
프로그램은 CPU-bound, I/O-bound 프로그램으로 나누자면 CPU-bound 프로그램은 행렬곱, 이미지처리 등 CPU 코어를 최대한 활용하는 프로그램이고, I/O-bound 프로그램은 네트워크, 데이터베이스 등 입출력을 기다리는데 많은 시간을 소비하는 프로그램을 말한다.
일반적으로 I/O-bound 프로그램의 경우 GIL이 성능에 영향이 없지만, CPU-bound 프로그램의 경우 GIL로 인해 멀티 쓰레드로 짠다고 해도 단일 쓰레드로 동작한다. 멀티쓰레드로 구현하면 실제로 단일 쓰레드로 구현한 것보다 느려질 수 있는데, 그 이유는 lock으로 인한 오버헤드 때문이다.
댓글남기기