python

Python - 멀티스레드, GIL (Global Interpreter Lock)

Jueuunn7 2024. 11. 25. 19:25

1. Python 인터프리터

파이썬 인터프리터는 파이썬의 코드를 한 줄씩 읽으면서 실행하는 프로그램을 말합니다. 이 파이썬 인터프리터를 구현한 구현체 중에 하나가 현재 표준 구현체로 사용되고 있는 CPython입니다. GIL은 CPython에서 적용되는 개념입니다.

 

2. GIL (Global Interpreter Lock)

2-1. GIL과 멀티스레딩

보통의 멀티 스레딩은 이런 방식으로 작동합니다.

여러개의 스레드가 서로 병렬로 동시에 실행되는 것을 말합니다.

하지만 파이썬은 조금 다르게 작동합니다.

일반적으로 두개의 스레드가 병렬로 작동하는 방식을 사용하지 않고 스레드가 각각 컨텍스트 스위칭을 하며 프로그램을 실행합니다.

이 이유는 파이썬에서는 일종의 뮤텍스 역할을 하는 GIL이 있기 때문입니다.

GIL은 하나의 자원에 접근할 수 있는 스레드를 1개로 제한하고 어떤 스레드가 해당 자원에 접근하면 Global Lock을 걸어서 다른 스레드가 접근하지 못하게 막습니다. 하지만 이런 방식은 컨텍스트 스위칭의 비용이 발생하면서 오히려 싱글 스레드 작업보다 성능이 낮아지는 현상이 발생하게 됩니다.

 

2. 멀티스레드를 왜 사용하지?

일반적인 상황에서의 멀티스레드는 성능을 저하시킬수도 있습니다. 하지만 모든 상황에서 무조건 성능이 낮아지는건 아닙니다.

단순 CPU 연산이 큰 경우에서는 싱글스레드가 더 좋은 모습을 보이지만 I/O 작업이나 sleep를 사용하는 작업에서는 멀티스레드가 더 좋은 성능을 보여줍니다.

sleep이 많이 들어가는 작업에서는 하나의 스레드에서 sleep을 실행하고 다른 스레드로 컨텍스트 스위칭 해서 작업 효율을 개선하고 I/O 작업이 크고 많아서 대기해야 하는 경우에서도 컨텍스트 스위칭을 이용해서 성능을 개선합니다.

 

2-2. GIL을 쓰는 이유 - GC

Python에서는 모든 것들이 객체로 되어 있습니다. 이 객체들을 관리하기 위해 공통으로 존재하는 필드가 있는데, 이것이 참조 횟수 입니다. 참조 횟수는 자신의 객체를 가리키고 있는 다른 객체들의 참조가 현재 몇 개 존재하는지 나타내는 필드입니다. GC는 어떤 객체가 참조 횟수가 0이 되면 메모리에서 해당 객체를 삭제하는 방식으로 작동합니다.

import sys

a = []  # 참조 1회
a.append(a)  # 참조 2회
count = sys.getrefcount(a)  # 참조 3회
print(count)  # 3

싱글 스레드인 상태에서는 참조 횟수와 GC가 레이스 컨디션이 발생할 일이 없는데 스레드가 여러개가 되버리면 참조 횟수에서 Race Condition이 발생하기 시작하고 연쇄적으로 GC도 의도와 다르게 작동하게 될 것입니다. 그래서 GIL이 스레드끼리 공유하는 자원을 Global Lock을 걸고 하나의 스레드에서만 작동할 수 있게 합니다.

 

3. 멀티스레딩을 쓰면서 병렬로 실행하기

멀티 프로세싱을 사용하지 않는 이상 CPython에서는 불가능하다. 대처법으로 pypy등 다른 파이썬 인터프리터 구현체들을 사용해서 개발을 하는 방법이 있다.

'python' 카테고리의 다른 글

Python - yield  (0) 2024.12.10
Python list append, extend, +=  (0) 2024.10.07
Python mutable 변경 가능, immutable 변경 불가능  (0) 2024.09.23
Python 잘 쓰는법: 리스트, 딕셔너리  (1) 2024.09.09
Python 다중 상속, super()  (0) 2024.09.02