11.1 Global Interpreter Lock
Global Interpreter Lock (GIL)은 CPython 인터프리터에서 한 번에 하나의 스레드만 Python 바이트 코드를 실행할 수 있도록 보장하는 메커니즘이야. GIL은 스레드 병렬 실행을 방지해서 특히 멀티코어 프로세서에서 멀티스레딩 프로그램의 성능에 부정적인 영향을 미칠 수 있어.
GIL이 존재하는 이유
메모리 관리 단순화: GIL은 메모리 관리와 garbage collection을 단순화해서 Python 구현을 쉽게 만들어.
스레드 안전성: GIL은 경쟁 상태를 방지해서 코드 실행을 스레드 안전하게 만들고, 락을 사용할 필요가 없어져.
GIL로 인한 문제점
제한된 성능: 계산 집약적인 작업을 수행하는 멀티스레딩 프로그램은 GIL의 제한으로 인해 멀티코어 프로세서의 이점을 완전히 활용할 수 없어.
성능 저하: 스레드를 많이 사용하는 프로그램은 스레드 간의 컨텍스트 전환 때문에 성능 저하를 겪을 수 있어.
현재 GIL을 "우회"하는 데에는 4가지 주요 방법이 있어:
11.2 멀티프로세싱 사용하기 (multiprocessing)
multiprocessing 모듈은 각 프로세스가 자신만의 Python 인터프리터와 메모리를 가지므로, 병렬로 실행되며 GIL에 의해 제한되지 않게 해줘.
예제:
import multiprocessing
def worker(num):
print(f'Worker: {num}')
def main():
processes = []
for i in range(5):
p = multiprocessing.Process(target=worker, args=(i,))
processes.append(p)
p.start()
for p in processes:
p.join()
main()
11.3 비동기 프로그래밍
asyncio를 사용한 비동기 프로그래밍은 메인 스레드를 차단하지 않고 작업을 병렬로 실행할 수 있도록 해줘. 이는 GIL을 직접적으로 우회하는 건 아니지만 대기 시간을 효율적으로 사용해서 다른 작업을 수행할 수 있게 해줘.
예제:
import asyncio
async def main():
await asyncio.sleep(1)
print('Hello')
asyncio.run(main())
11.4 자체 스레드 관리 라이브러리 사용하기
NumPy나 SciPy 같은 일부 라이브러리는 C로 작성되어 자체 스레드 관리 메커니즘을 사용해서 GIL을 우회하고 계산을 위해 멀티코어 프로세서를 효과적으로 사용할 수 있게 해줘.
바로 이 점이 Python의 주요 성공 원인 중 하나야. 아무리 Python이 느리다고 해도 복잡한 계산들은 C/C++으로 다시 작성되어 모든 프로세서 코어 또는 심지어 GPU의 코어에서도 실행돼. 현대 GPU에는 수천 개의 코어가 있는 경우도 많아.
그래서 언어가 빠르든 느리든 중요하지 않게 되는데, 모든 자원 소모적인 계산을 외부 라이브러리가 처리하거나 완전히 원격 데이터 센터로 옮겨지니깐.
11.5 Python 인터프리터 외부에서 계산 실행하기
C/C++ 또는 다른 언어의 확장을 사용해서 Python 인터프리터 외부에서 계산을 수행하고 결과를 반환할 수 있어. 이는 집약적인 계산을 수행할 때 GIL 차단을 피할 수 있게 해줘.
Cython 사용 예제:
# example.pyx
def compute(int n):
cdef int i, result = 0
for i in range(n):
result += i
return result
컴파일 및 사용:
cythonize -i example.pyx
import example
print(example.compute(1000000))
GO TO FULL VERSION