GIL (Global Interpreter Lock)
여러 개의 스레드가 파이썬 바이트코드를 한번에 하나만 사용할 수 있게 락을 거는것, 파이썬 인터프리터를 제어할 수 있도록하는 뮤택스.
등장 배경
파이썬은 레퍼런스 카운팅 방식으로 메모리를 관리하는데, 여러개의 스레드가 동시에 객체를 참조할 경우 Race Condition 이 발생하여, 메모리 누수가 발생할 수 있음. 이러한 문제점을 해결하기 위해 GIL 개념이 도입됨
객체마다도 Lock 을 걸 수 있겠지만 비효율적이기 때문에 Global 한 락을 하나 설정하여 사용하는 방식으로 접근함
성능 차이
CPU Bound
단일 스레드
# 단일 스레드 수행시간
CNT = 1000000
def run(n):
ret = 0
for i in range(n):
ret += 1
return ret
start_time = time.time()
run(CNT)
end_time = time.time()
print("running time: ", end_time - start_time)
> \> running time: 0.06689858436584473
멀티 스레드
# 멀티 스레드 수행시간
CNT = 1000000
def run(n):
ret = 0
for i in range(n):
ret += 1
return ret
t1 = Thread(target=run, args=(CNT // 2,))
t2 = Thread(target=run, args=(CNT // 2,))
start_time = time.time()
t1.start()
t2.start()
t1.join()
t2.join()
end_time = time.time()
print("running time: ", end_time - start_time)
> \> running time: 0.08830523490905762
위 결과를 보면 단일 스레드의 성능이 멀티스레드의 성능보다 좋은 것을 확인할 수 있음 이는 GIL 때문임, CPU Bound 한 프로그램에 대해서 GIL 은 락을 걸어 성능향상보다 오히려 병목을 만들 수 있음. 그렇다면 I/O bound 작업에 대해서는 어떨까
I/O Bound
import threading
import time
CNT = 1000000
def network_job():
time.sleep(0.2)
def cpu_bound_job():
ret = 0
for i in range(1000000):
ret += 1
def working():
network_job()
cpu_bound_job()
network_job()
cpu_bound_job()
start_time = time.time()
working()
working()
end_time = time.time()
print("Single thread running time: ", end_time - start_time)
t1 = Thread(target=working)
t2 = Thread(target=working)
start_time = time.time()
t1.start()
t2.start()
t1.join()
t2.join()
end_time = time.time()
print("Multi thread running time: ", end_time - start_time)
> \>> Single thread running time: 1.2877919673919678
> \>> Multi thread running time: 0.660606861114502
I/O bound 작업에서는 멀티스레드의 성능이 더 향상 된것을 볼 수 있음. 이는 sleep 함수를 호출 할때 cpu 가 idle 상태로 변하기 때문임
참고
'언어 > 파이썬' 카테고리의 다른 글
[파이썬] - Dependency Injection (의존성 주입) (0) | 2024.01.20 |
---|---|
[파이썬] - 메모리 관리 기법 (1) (0) | 2024.01.08 |
[파이썬] - Mutable vs Immutable Objects (1) | 2024.01.04 |