오늘은 파이썬에서 멀티스레딩을 구현할 때 지나칠 수 없는 주제인 GIL에 대해서 알아보려고 한다.

GIL이 적용된 이유를 정확하게 이해하기 위해서는 파이썬의 Garbage Collection과 Reference Counting에 대한 공부도 필요한데, 이 개념은 이후에 다른 포스트에서 정리하려고 한다.

GIL(Global Interpreter Lock)이란

프로그래밍 언어의 인터프리터에서 스레드 실행을 동기화하기 위해, 한번에 하나의 스레드만 실행할 수 있도록 하는 메커니즘을 말하며, 파이썬의 기본 인터프리터인 CPython이 GIL을 사용한다.

 

Python wiki 에서는 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

 

*️⃣ [참고] thread safety란?

스레드 안전(thread safety)은 멀티 스레드 프로그래밍에서 일반적으로 어떤 함수나 변수, 혹은 객체가 여러 스레드로부터 동시에 접근이 이루어져도 프로그램의 실행에 문제가 없음을 뜻한다.
보다 엄밀하게는 하나의 함수가 한 스레드로부터 호출되어 실행 중일 때, 다른 스레드가 그 함수를 호출하여 동시에 함께 실행되더라도 각 스레드에서의 함수의 수행 결과가 올바로 나오는 것으로 정의한다.

 

단순히 멀티 스레딩을 사용하면 처리 속도가 더 빨라질 것 같지만, 파이썬에서는 항상 그런 것은 아니다. 아래의 그림처럼 동시에 3개의 스레드를 사용하는 경우를 살펴보자. 

파이썬에서는 GIL 때문에 3개의 스레드가 병렬적으로 일을 처리하지 못하고, 한번에 하나의 스레드만 실행될 수 있다. 파이썬에서 멀티 스레딩을 사용하는 경우, 싱글 스레드를 사용할 때보다 처리 시간이 더 길어지는 경우가 있는데, 이는 GIL에 의해 작업이 병렬적으로 처리되지 못함과 더불어, 멀티 스레딩의 경우 context switching 비용이 발생하기 때문이다.

그렇다면 CPython은 이런 비효율적인 GIL을 왜 사용하는 것일까?

GIL을 사용하는 이유

이후에 다른 포스트에서 정리하긴 하겠지만, 파이썬에서는 모든 것이 객체(Object)이다. 파이썬의 GC(Garbage Collector)가 작동하기 위해서 모든 객체는 reference count라는 필드를 가지고 있다. 즉, 모든 객체가 자신이 참조된 횟수를 가지고 있다는 말이다. 파이썬의 GC는 기본적으로 객체의 reference count가 0이 되면 객체의 메모리 할당을 해제하는 방식으로 작동한다. 

파이썬에서 멀티 스레딩을 사용하는 경우, 여러 개의 스레드에서 동시에 하나의 객체에 접근하게 되면 Race condition이 발생하고, 이에 따라 객체의 reference count를 정확하게 관리하기 어려워진다. 

위에서 GIL을 mutex라고 표현했던 것이 기억나는가? mutex는 race condition을 방지하기 위한 해결책 중에 하나로, 쉽게 말해 공유 객체에 접근할 수 있는 권한을 주는 키(key)라고 생각할 수 있다. 각 스레드는 이 mutex를 얻었을 때만 공유 객체에 접근할 수 있다. 이런 방식으로 하나의 공유 객체에 동시 접근하는 스레드를 막는 방법을 사용하는 것이 바로 mutex를 이용한 방식이다. 

정리하면, 파이썬에서는 GC의 올바른 작동을 위해 필요한 객체의 reference count를 정확하게 관리하기 위해 일종의 mutex인 GIL을 사용하여, 여러 개의 스레드가 공유 객체에 동시 접근하는 것을 막는 것이다. 

 

*️⃣ [참고] Race condition이란?

경쟁 상태(race condition)란 둘 이상의 입력 또는 조작의 타이밍이나 순서 등이 결과값에 영향을 줄 수 있는 상태를 말한다.

파이썬에서 멀티 스레딩은 언제 사용할까?

GIL에 대해서 공부하다 보니, 파이썬에서 멀티 스레딩은 사용할 이유가 없는 것 아닌가 하는 생각이 든다. 그럼 멀티 스레딩은 안쓰는 것이 정답일까?

IO 관련 작업이 많은 경우에는 멀티 스레딩이 도움이 될 수 있다!  파이썬은 IO, Sleep 등의 연산에 의해 CPU가 아무런 작업도 하지않고 기다리는 경우, 다른 스레드로 context switching을 하도록 되어 있다. 따라서 싱글 스레드를 사용하는 것보다, 멀티 스레드를 사용해서 IO 관련 작업을 진행할 때, 처리 속도가 향상될 수 있다. 

정리하면, CPU 연산이 많은 작업은 멀티 스레딩을 통해 처리할 때 GIL과 context switching 비용 때문에 비효율적이고, CPU 연산이 적고 IO 관련 작업이 많은 경우에는 처리 속도에 도움이 될 수 있다.

 

하지만 요즘에는 파이썬에서 싱글스레드로 IO 작업의 비동기 처리가 가능하기 때문에, 파이썬에서 멀티 스레딩의 필요성이 점차 줄어들지 않을까하는 생각이 든다. 싱글 스레드라서 context switching 비용도 절약할 수 있어 더 효율적인 방식이기 때문이다.

파이썬에서 비동기가 궁금하다면 이전 글을 확인하자!

 

[Python] 파이썬에서의 비동기 이해하기 (feat. asyncio)

이번 글에서는 async/await의 동작을 이해하고, 파이썬에서 비동기가 어떤 방식으로 동작하는지 asyncio를 통해 이야기해보려 한다. 먼저 이번 글에서 asyncio의 동작 원리에 대한 부분은 아래 블로그

seohyeoniii.tistory.com

복사했습니다!