CS🏅/파이썬 🖤

GIL

Dobby98 2023. 7. 25. 02:04

요즘 프로그램을 코딩하면서 가장 먼저 우선시하려고 노력하는 부분이 내가 작성한 프로그램의 처리속도가 과연 효율적인지? 또는 더 빠른 방법으로 처리할 수는 없는지 이다.

 

이러한 고민을 하다보면 Multi - Processing or Multi-threading을 마주 칠수 밖에 없고 

CS 지식이 부족하면 조금 이해하기 어렵기 때문에 추가적인 공부를 하고 있다

 

공부를 하다보면 파이썬은 GIL이 있다라는 말을 자주 마주치게 되었다

따라서 이번 시간에는 이러한 GIL에 대해서 정리하고 다양한 정리를 해보려고 한다


1. 그래서 GIL이 뭔데?

우선 GIL을 이해하기전에 Python의 인터프리터를 이해 해야한다.

 

Python의 인터프리터란, 인터프리터 언어를 생각하면 쉽게 이해할 수 있는데

내가 작성한 파이썬 코드를 실행한다고 했을 때 작성한 코드를 한 줄 씩 읽으면서 실행하는 프로그램을 의미한다. 파이썬에서 표준으로 받아 들여지고 있는 인터프리터는 CPython으로 C언어 기반으로 구현된 인터프리터이다

 

자, 그럼 이제 인터프리터를 이해했다 그럼 GIL을 살펴보자

용어를 풀어서 설명하면 Global Interpreter Lock 로 한국어로 풀어보면 전역 인터프리터 락이다.

 

그러니까 여러개의 쓰레드가 존재한다고 했을 때 이 존재하는 여러 쓰레드가 동시에 하나의 파이썬 코드를 실행하지 못하게 막아 놓은 것이다

 

즉, Python 인터프리터는 한 시점에 하나의 쓰레드에 의해서만 실행된다

 

얼핏 보면 멀티 쓰레딩이 불가능하다는 것처럼 보이지만 원래 멀티 코어의 멀티 쓰레딩 시에는 여러개의 쓰레드가 코어 에서 병렬로 실행될 수 있지만 Python에서는 그 병렬 실행이 불가능하다는 것 이다

물론 프로그램을 사용하는 우리한테는 병렬적으로 실행되는 것 처럼 보이겠지만 사실은 병렬로 실행되고 있는 것은 아니다. 위의 사진을 보면 쉽게 확인할 수 있다


2. 그럼 왜 GIL가 필요한데?

아니 당연히 병렬로 처리하면 빠르고 좋은게 아닐까?

물론 속도면에서는 매우 효율적이다.

 

그럼 왜 Python 에서는 이러한 제약을 걸어둔 것일까?

 

바로 메모리의 안정성을 보장하기 위해서이다.

이를 이해하기 위해서는 파이썬의 메모리 관리 방식에 대해서 이해해야 한다.

import sys

# 변수x : 참조 횟수 : 1
x = []

# x의 참조 횟수 : 2
y = x

sys.getrefcount(x)

#-> 참조 횟수 총 3번

파이썬 코드에서는 참조 횟수 라는 것이 있다

이는 Reference Count로 그 객체를 가르키는 참조가 몇 개 존재하는 지를 나타내는 것으로,

 

이 참조 횟수가 0이 되면 GC (Garbage Collection)이 발생하게 되어 해당 객체를 메모리에서 삭제한다

.

만약 두 스레드가 동시에 하나의 객체의 참조횟수를 늘리거나 줄이게 되면 Race Condition이 발생하게 되는데 이는 메모리에 누수가 발생하여 실제 객체에 대한 참조가 남아 있음에도 불구하고 GC가 발생하는 불상사가 발생할 수 있다

 

따라서 GIL를 활용하여 이러한 문제를 방지하고 메모리의 안정성을 확보하기 위해서 설계된 방법이라고 할 수 있다


3. 그래서 우리가 고려해야 할 부분은 무엇일까?

그러면 멀티 쓰레딩은 파이썬에서 무조건 안좋지 않을까?

굳이 여러 쓰레드로 바꿔가면서 실행하는 것보다 싱글 스레드로 순차적으로 처리하는 방법이 더 효율적이지 않을까?

 

반은 맞고 반은 틀린 말이다

 

만약 우리가 작성한 코드가 CPU bounding 이라면 멀티 쓰레딩은 위의 이유로 비효율적이다

각 쓰레딩을 전환하면서 발생하는 Context Switching에서 시간만 더 잡아 먹기 때문이다

 

하지만!! I/O bounding 이라면 이야기가 달라진다

 

애초에 GIL은 CPU 연산 과정에서 공유 자원에 대한 Race Condition 문제에 대해서 보호하기 위해서 설계 된것이기 때문에 만약 Input과 output에 기반이 코드라면 중간 중간에 CPU가 노는 시간이 존재할 것이고 이는 기다리기만 하는 시간을 다른 쓰레드로 변환시켜 실행할 수 있는 효율적인 방법이 될 수 있다

 

즉, 한 줄로 요약하자면 CPU bounding의 python 코드라면 멀티쓰레딩은 비효율적 일 수 있지만 I/O bounding이라면 오히려 좋은 성능을 보일 수 있고 효율적일 수 있다