파이썬은 한번에 하나의 thread만 실행할 수 있도록 인터프리터에 락을 걸었고, 그것을 GIL이라고 한다.
바로 파이썬의 객체 생성, 소멸에 대한 관리의 부분에서 race condition이 발생할 수 있기 때문에.
파이썬 → CPython에서는 에서는 객체의 생성과 소멸을 reference count를 통해서 관리한다. 어떤 객체가 생성되고, 해당 객체를 참조하고 있는 개수에 따라 reference count를 증가시킨다.
다음은 CPython의 object 객체에 대한 선언과 reference 관리 부분이다.
typedef struct _object {
_PyObject_HEAD_EXTRA Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
} PyObject;
#define Py_INCREF(op) ( \\
_Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA \\
((PyObject *)(op))->ob_refcnt++)
#define Py_DECREF(op) \\
do { \\
PyObject *_py_decref_tmp = (PyObject *)(op); \\
if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA \\
--(_py_decref_tmp)->ob_refcnt != 0) \\
_Py_CHECK_REFCNT(_py_decref_tmp) \\
else \\
_Py_Dealloc(_py_decref_tmp); \\
} while (0)
보면 ob_refcnt
라는 값을 통해서 reference count가 관리되고 있는 것을 알 수 있다.
그리고 저렇게 올라가는 refcnt에 대해서 thread-safe한 처리가 따로 이루어지고 있지 않다.
그렇다면, 이 객체에 대해서 thread-safe하게 처리해주려면 해당 decref, incref 동작에 대해서 thread safe할 수 있도록 mutex lock을 걸어주면 될 것이다.
→ 하지만 이렇게되면 모든 object 하나하나에 대해서 mutex가 필요할 것이다. 여러개의 mutex는 성능적으로 많은 손해를 볼 뿐 아니라 데드락시 치명적인 상황에 처할 수 있다.
이를 해결하기 위해서 각 객체에 lock을 거는 것이 아니라 한 인터프리터가 한 시점에 하나의 python코드를 실행할 수 있도록 함으로써 이러한 thread safe 문제를 해결했다.
이는 사실상 한 process에서 한 thread만이 python bytecode를 실행하기 위해 global interpreter lock을 잡아야 하는 것과 같다.
그럼 이렇게 되면, 어차피 하나의 python 인터프리터는 하나의 python code밖에 실행하지 못한다. 그렇게 되면 multithread를 사용할 필요가 없는데 왜 이것이 존재하는 걸까?
그 이유는 바로 IO bound job의 존재 덕분이다.
interpreter lock은 아까 말한대로, CPU에서의 실행에 국한된 개념이다. 한 cpu가 점유하는 한 프로세스에서는 한 thread밖에 실행할 수 없다.