[PYTHON]GIL & Multi Thread

박민하·2022년 7월 28일
2

PYTHON

목록 보기
9/11
post-thumbnail
  • program : 작업을 위해 실행하는 파일
  • process : 운영체제가 생성하는 작업의 단위(=컴퓨터에서 실행중인 프로그램)
  • thread : process 안에서 공유되는 메모리를 바탕으로 생성하는 작업의 실행 단위
    • Thread safe : 하나의 스레드가 자원에 접근하여 작업을 수행하는 상태
    • race condition(경쟁상태) : 여러 스레드가 하나의 공유 자원이 동시에 접근하면서 발생하는 문제
    • mutex(상호배제) : 공유 자원에 하나의 스레드만 진입하며 작업을 처리할 수 있도록 만들어진 lock 개념
  • garbage collection : 파이썬에서 쓰이는 메모리 관리 기법. 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역을 해제하는 기능
    • Reference counting : 쓰레기 수집(garbage collection)의 한 방식
  • context swich : 프로세스/스레드 전환. 사용중이던 context(프로세스, 스레드)를 저장하고 다른 context 실행

✅ GIL(Global Interpreter Lock)

  파이썬에서 GIL란, 파이썬코드가 실행될 때 여러개의 스레드 중 하나의 스레드만 실행될 수 있도록 하는 mutex다. 즉, 하나의 스레드가 모든 자원(리소스)을 차지하고 lock을 걸면, lock을 풀 때 까지 다른 스레드는 실행할 수 없도록 접근을 막는 것이다.

  예를 들어, 하나의 함수 sum을 두 스레드 thread1, thread2가 사용하고자 한다.

def sum(x, y):
    return x+y

thread1 = sum(1,2)
thread2 = sum(3,4)

  한 스레드가 함수를 사용 중일 때는 다른 다른 스레드는 대기 상태에 빠진다. 함수를 실행하는데 1초가 걸린다고 하면 두 스레드가 완료되는 시점은 2초가 지난 뒤일것이다.

  이렇듯 GIL 방식은 두 스레드를 전환(context swiching)하는데 시간이 걸리는 데다가 병렬 실행(스레딩 작업)이 제한된다. 파이썬이 다른 언어보다 속도가 느린 이유다.

✅ 파이썬은 왜 GIL을 채택했는가

  GIL는 스레드를 직렬로 실행시키면서 멀티스레딩의 장점을 없애버린다. 그럼 왜 파이썬은 GIL를 사용할까? 그 이유는 파이썬의 thread safe하지 않은 garbage collection 메모리 관리 정책을 사용하기 때문이다. 동적으로 메모리를 할당한 뒤, 그 메모리 공간이 필요가 없어지면 할당을 해지하는 관리 방법인데 이는 다중스레드가 발생하면 메모리 할당 순서가 꼬여버린다.

  이게 무슨말인가 하면, 아래의 예시를 보자.

  x라는 변수에 1씩 100만번을 더하는 함수를 만들고, 이를 실행하는 두 스레드를 만들었다.

import threading
x = 0
 
def foo():
    global x
    for _ in range(1000000):
        x += 1
 
thread1 = threading.Thread(target=foo)
thread2 = threading.Thread(target=foo)
 
thread1.start()
thread2.start()
 
thread1.join()
thread2.join()
print(x)
print("time :", time.time() - start) 

  이 코드를 돌려보면 이런 결과값이 나온다.

  100만을 더하는 함수를 두 번 돌렸으니 결과값이 200만이 나와야 하는데 그보다 못한 수가 나온다. 그리고 코드를 실행할 때마다 결과값이 다르다. 두 연산식이 꼬여버린(race condition) 것이다. 위 함수에 lock을 걸어서 한 스레드의 작업이 끝날 때까지 다른 스레드가 접근할 수 없도록 하고 다시 실행을 해보자.

import threading
import time

start = time.time() 
LOCK = threading.Lock()
x = 0
 
def foo():
    LOCK.acquire()
    global x
    for _ in range(1000000):
        x += 1
    LOCK.release()
 
thread1 = threading.Thread(target=foo)
thread2 = threading.Thread(target=foo)
 
thread1.start()
thread2.start()
 
thread1.join()
thread2.join()
print(x)
print("time :", time.time() - start) 

  이제야 200만이 나온다. 심지어 속도도 조금 더 빨라졌다!

✅ 파이썬에서 multi thread는 느릴 수밖에 없나?

  GIL이 적용되면 직렬 실행이 안되니 멀티 스레드는 무조건 느릴거라고 생각할 수 있다. 하지만 위에서 든 예시를 보면 알겠지만 그렇지만도 않다.

  GIL은 cpu 동작에서 적용이 되고, 스레드가 I/O 작업을 실행하는 동안에는 다른 스레드가 cpu 동작을 동시에 실행할 수 있다. 따라서 cpu 동작이 많지 않고 I/O동작이 더 많은 프로그램에서는 멀티 스레드의 효과를 얻을 수 있다.

  1. cpu 작업
  • 곱셈등의 연산, 이미지 처리 등
  • I/O 제외한 연산
  1. I/O 작업
  • 파일 읽기/쓰기, 네트워크 통신 등
  • 멀티스레드가 발생해도 GIL가 없어서 속도가 빠르다

[참고사이트]

Python 공식문서 - GlobalInterpreterLock
Python GIL(Global Interpreter Lock)
python gil이 있으니까 thread safe 할까요?
[Python] 파이썬 멀티 쓰레드(thread)와 멀티 프로세스(process)
Python Global Interpreter Lock Tutorial

profile
backend developer 🐌

0개의 댓글