[Python] GIL(Global Interpreter Lock)이란?
GIL(Global Interpreter Lock)
GIL은 파이썬에만 존재하는 특별한 개념으로 다수의 쓰레드가 파이썬 코드를 동시에 실행하지 못하도록 막는 일종의 뮤텍스(Mutex)이다. 즉, 하나의 스레드에만 모든 자원을 할당하고 다른 스레드는 접근할 수 없게 막는 역할을 GIL이 수행한다.
뮤텍스(Mutex) 란 ?
👉 멀티스레딩 환경에서 여러 개의 스레드가 어떠한 공유자원에 접근하기 위해 가지고 있어야하는 일종의 열쇠와 같은 것
프로세스(Process)와 스레드(Thread)
- 프로세스(Process) : 메모리 상에서 실행중인 프로그램, 자신만의 고유공간과 자원을 할당받음
- 1개의 프로세스는 최소 1개 이상의 스레드를 갖고 있음
- 각각 별도의 주소공간을 독립적으로 할당 받음 (code, heap, stack)
- 스레드(Thread) : 프로세스 안에서 실질적으로 작업을 수행하는 흐름 단위, 다른 스레드와 공간과 자원을 공유
- 프로세스 내의 스레드는 서로 메모리를 공유
- 하나의 프로세스가 생성되면 하나의 스레드(메인스레드)가 생성됨
- 스레드는 프로세스 내에서 stack만 따로 할당받고 code, heap, data 영역은 서로 공유
- 멀티 프로세스(Multi Process) : 하나의 응용 프로그램을 여러개의 프로세스로 구성하여 각 프로세스가 하나의 작업을 처리하도록 하는 것
- 하나의 프로세스에 문제가 발생해도 다른 프로세스에 영향을 끼치지 않음
- Context Switching으로 인한 성능 저하 우려
- 멀티 스레드(Multi Thread) : 하나의 프로세스가 여러 작업을 여러 스레드를 사용해 동시에 처리하도록 하는 것
- Context Switching 할 때 공유하고 있는 메모리만큼 메모리 자원을 아낄 수 있어서 빠름
- 시스템 자원과 처리비용 감소
- 디버깅이 까다로움
- 하나의 스레드에 문제가 생기면 전체적인 프로세스에 영향을 끼침
GIL이 필요한 이유
먼저, Python은 동적 프로그래밍 언어의 형태를 따르고 있다.
특히 변수 타입이 동적이기 때문에 매번 변수의 타입을 확인해야 하므로 이런 특징이 속도 성능을 저하시키는 원인이 된다.
Python은 이런 단점을 보완하기 위해 Python을 C언어로 구현한 구현체인 CPython을 표준 인터프리터로 사용하고 있고, 이 CPython을 내부 구현체로 사용하는 모듈이나 함수 등 은 GIL의 특성을 가지고 있다.
Python에서 멀티스레드를 사용하면 GIL 특성에 따라 각 스레드가 GIL을 얻어 동작하고 이 때에 다른 스레드들은 모두 동작을 멈추게 되므로 오히려 싱글스레드인 경우보다 시간이 더 오래 소요되는 문제가 발생하기도 한다.
그렇다면 싱글스레드일 때보다 멀티스레드를 사용할 때가 오히려 더 좋지않은 성능을 보이는데 도대체 왜 GIL을 사용하는 걸까 ?
정확한 이유를 알기 전에 먼저 파이썬의 메모리 관리법을 알아야 한다.
Python에서의 모든 것은 객체(Object)이고, 각 객체는 참조횟수(Reference Count)를 저장하고 있다.
참조횟수(Reference Count)란 ?
👉 각 객체들이 참조되는 횟수, 참조 여부에 따라 증감
Python에서의 GC(Garbage Collection)는 이 참조횟수가 0이 되면 그 객체를 메모리에서 삭제하는 방식으로 동작한다.
그런데, 여러 개의 스레드가 한 객체에 동시에 접근하게 되면 객체의 Reference Count에 Race Condition이 발생할 수 있고 이는 GC의 정상적인 동작에 문제를 일으킬 수 있기 때문에
Thread Safe 하지 않은 결과가 생기기 때문이다.
Race Condition ?
👉 하나의 자원에 동시에 접근함으로써 값이 올바르지 않게 읽히거나 쓰일 수 있는 상태
따라서, GC의 올바른 동작을 보장하기 위해서는 모든 객체에 Mutex가 필요하기 때문에 Python에서는 GIL을 사용하는 것이다.
Python에서 멀티스레드는 항상 안좋을까 ?
위 내용에서 보면 Python에서는 GIL 떄문에 멀티스레드를 사용할 이유가 없어보이지만 일부 외부연산(I/O 작업, sleep 등)에서는 멀티스레드가 좋은 성능을 보여준다.
I/O 작업이나 sleep과 같은 경우에는 CPU가 요청을 걸어두고 아무것도 하지않고 대기하기 때문에 다른 스레드로의 문맥전환(Context Switching)하게 되어 효율이 개선된다.
GIL을 우회할 수 있는 방법
GIL을 우회하여 CPU 병렬연산을 할 수 있는 방법에는
- 멀티 프로세싱 사용
- CPython이 아닌 다른 인터프리터 구현체 사용
이 있다.
먼저, 멀티프로세싱을 사용하면 프로세스 각각이 독자적인 메모리공간을 가지므로 GIL을 우회할 수 있지만, 더 많은 메모리를 필요로하고 문맥전환(Context Switching) 비용이 큰 단점이 있다.
또한, CPython이 아닌 다른 인터프리터 구현체를 사용하는 방법 또한 GIL을 우회할 수 있다.
댓글남기기