[Python] 파이썬의 GIL (Global Interpreter Lock)

2021. 10. 22. 19:13· 언어/Python
728x90
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.

CPython에서 global intertreter lock(GIL)은 멀티 스레드가 파이썬 바이트코드들을 동시에 실행시키는 것을 막으며 파이썬 object에 대한 접근을 보호하는 뮤택스이다. GIL은 경쟁상태(race condition)을 예방하고 thread-safety를 보장한다.

https://wiki.python.org/moin/GlobalInterpreterLock

 

파이썬은 코드를 사전에 컴파일하지 않고, 실행 중 한 줄 한 줄 인터프리터를 통해 코드를 해석하며 작동하는 인터프리터 언어이다.

 

그리고 GIL, Global Interpreter Lock은 말 그대로 이러한 파이썬 코드를 해석하는 인터프리터를 글로벌하게 락한다는 이야기이다.

 

다시 말해, 하나의 프로세스 내에서 하나의 스레드가 파이썬의 인터프리터를 실행 중일 때 다른 스레드는 인터프리터를 사용할 수 없다.

 

따라서, 우리가 어떤 작업을 여러 갈래로 나누어 동시에 처리하기 위해 멀티스레딩을 사용할 때, 해당 작업이 파이썬으로 작동된다면 스레드가 아무리 많아도 인터프리터는 한 순간에 하나만 작동되므로 우리가 기대하는 멀티스레딩의 성능을 기대하기 힘들다.

 

오히려 스레드 간 컨텍스트 스위칭 비용 때문에 작업 성능이 저하될 우려도 존재한다.

https://medium.com/@mjhans83/python-gil-f940eac0bef9

 

파이썬은 왜 인터프리터에 락을 걸어놓았을까?

GIL의 존재 이유는 파이썬의 메모리 관리 방식이 Thread-Safe하지 않기 떄문이다.

 

파이썬은 객체가 참조되는 횟수 즉, reference count를 이용해 메모리 관리를 한다.

 

특정 객체가 참조될 때마다 reference count를 1씩 증가시켜주고, 참조가 해체될 때는 1씩 감소시키며 reference count가 0이 되면 해당 객체는 메모리에서 삭제된다.

 

이 reference count는 sys의 getrefcount함수를 사용해서 확인할 수 있다.

# reference count 테스트
import sys

class RefTest:
    def __init__(self):
        self.data = 1

test1 = RefTest()
print(sys.getrefcount(test1))
# 2
# test1을 정의할 때 한 번, getrefcount함수 사용할 때 한 번, 총 2번 참조

그리고, 멀티 스레드 환경에서 각 스레드들은 자신만의 독자적인 스택영역을 제외한 메모리를 다른 스레드들과 공유하는데, reference count 또한 이에 포함된다.

 

따라서, 만약 두 개 이상의 스레드에서 동일한 객체를 동시에 참조하게 되면 해당 객체의 reference count 값에 대한 Race Condition이 발생하고, 결국엔 부정확한 값이 reference count에 기록될 수 있다.

 

파이썬은 이러한 위험을 막기 위해 아예 인터프리터가 한 번에 한 개의 스레드에서만 동작하도록 만들게 된 것이다.

 

그렇다면 파이썬에서는 멀티 스레드를 사용하면 안될까?

파이썬에서 멀티 스레딩을 사용하는 건 무조건 불리하기만할까?

 

물론 그렇지 않다.

 

파이썬의 공식문서에 의하면 파이썬 역시 멀티스레드 기반의 병렬처리 모듈을 제공한다.

https://docs.python.org/ko/3.8/library/threading.html

 

만약 파이썬에서 멀티스레딩이 불리하기만 했다면 이런 모듈 자체를 안 만들지 않았을까 싶다.

 

GIL은 파이썬의 런타임과의 상호작용 여부에 의해 영향을 받는다.

 

따라서, 파이썬 런타임과 상호작용을 하는 CPU-Bound 작업들에서는 GIL이 계속 영향을 주기 때문에 멀티 스레드를 이용하더라도 성능 향상을 기대하기 힘든 반면, I/O-Bound 작업에선 극적이진 않더라도 어느 정도의 성능 향상을 기대할 수 있다.

 

CPU-Bound job

import time

TARGET_COUNT = 100000000

def count_down(n):
    while n > 0:
        n -= 1

    print("카운트 다운 완료")


start_time = time.time()
count_down(TARGET_COUNT)
end_time = time.time()

print("it took ", end_time - start_time)

# 카운트 다운 완료
# it took  4.626717805862427
import time
from threading import Thread

TARGET_COUNT = 100000000

def count_down(n):
    while n > 0:
        n -= 1

    print("카운트 다운 완료")

thread1 = Thread(target=count_down, args=(TARGET_COUNT // 2,))
thread2 = Thread(target=count_down, args=(TARGET_COUNT // 2,))

start_time = time.time()

thread1.start()
thread2.start()
thread1.join()
thread2.join()

end_time = time.time()

print("it took ", end_time - start_time)

# 카운트 다운 완료
# it took  4.598112106323242

1억부터 0까지 카운트다운을 해주는 함수를 실행시켜보자.

 

while문 안에서 계속해서 연산을 해주는 CPU-Bound한 작업이다.

 

보다시피, 스레드를 사용하지 않은 경우와 사용한 경우 사이에 성능의 유의미한 차이가 존재하지 않음을 확인할 수 있다.

 

I/O Bound Job

CPU-Bound 작업과는 다르게, I/O Bound 작업에서는 그래도 단일 스레드보다는 성능의 향상을 조금 더 기대할 수 있다.

 

물론, 한 번에 하나의 스레드만이 인터프리터를 작동시킨다는 것은 동일하다는 점에서 무슨 성능 향상이 있을까 싶지만, 프로그램 실행 중 I/O 인터럽트 발생 시 파이썬은 GIL을 release한다.

 

이 때, 단일 스레드라면 I/O 작업이 완료될 때까지 다른 작업들이 block되지만, 멀티 스레드를 사용한다면 기존 실행 중이던 스레드가 GIL을 release하자마자 다른 스레드가 곧바로 GIL을 넘겨받게 되므로, I/O 작업 처리 시 발생하는 딜레이들을 줄일 수 있다.

 

 

그 외에도, 파이썬의 Numpy 라이브러리를 이용한 number crunching 작업도 GIL 밖에서 처리된다고 한다.

 

이로써 Numpy를 이용한 연산이 일반적인 파이썬 코드와 비교해 어떻게 이렇게 빠를 수 있는지도 이해할 수 있게 되었다.

import numpy as np

# datas의 분산을 구하는 코드
def variance(datas):
    x_ = np.mean(datas)
    var = 0
    
    for xi in datas:
        var += (xi - x_) ** 2
    
    return var / len(datas)

물론 Numpy 라이브러리에 구현된 코드는 내가 짠 코드와 무언가 다르겠지만, 동일한 크기의 데이터에 대하여 3.91초 vs 21.7마이크로초로 미친듯한 차이를 보여준다.

저작자표시 (새창열림)

'언어 > Python' 카테고리의 다른 글

[Python] 파이썬의 매직 메소드 (Magic Method)  (0) 2022.01.26
[Python] 아스키코드 사용하기  (0) 2022.01.19
[Python] heapq(우선순위 큐) 사용법  (0) 2021.09.19
[Python] 집합 자료형 다루기  (0) 2021.08.04
[Python] collections 모듈의 Counter 함수  (2) 2021.04.04
'언어/Python' 카테고리의 다른 글
  • [Python] 파이썬의 매직 메소드 (Magic Method)
  • [Python] 아스키코드 사용하기
  • [Python] heapq(우선순위 큐) 사용법
  • [Python] 집합 자료형 다루기
SeongOnion
SeongOnion
서버는 꺼지지 않아요
SeongOnion
조무래기 코딩
SeongOnion
전체
오늘
어제
  • 분류 전체보기 (166)
    • 알고리즘 (81)
      • 이론 (8)
      • 문제풀이 (73)
    • 언어 (15)
      • Python (9)
      • JavaScript (1)
      • JAVA (5)
    • 데이터베이스 (5)
    • 프레임워크 (15)
      • Django (7)
      • Spring (8)
    • 그 외 공부 (37)
      • 운영체제 (1)
      • 자료구조 (14)
      • 네트워크 (5)
      • CS (2)
      • 기타 (6)
      • 트러블 슈팅 (9)
    • 프로젝트 (0)
    • 개발자취 (8)
    • 회고 (3)
    • 주저리주저리 (1)
    • 기타 (비개발) (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 파이썬
  • 프로그래머스
  • DRF
  • Django
  • 정렬 알고리즘
  • 투 포인터 알고리즘
  • 백준
  • 이진탐색
  • 코딩
  • 자바
  • 컨트리뷰트
  • 스택
  • 장고
  • 회고
  • 개발자
  • 그리디알고리즘
  • 큐
  • 소수
  • BFS
  • 트러블 슈팅
  • 알고리즘
  • DP
  • spring
  • BFS/DFS
  • 데이터베이스
  • 오픈소스
  • 에라토스테네스의 체
  • 브루트포스
  • 코딩테스트
  • 웹

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.2
SeongOnion
[Python] 파이썬의 GIL (Global Interpreter Lock)
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.