Music for reading(spotify)

무엇을 오해하고 있었나?

파이썬에는 GIL이 있으니 사실상 싱글스레드에서 동작하는거나 마찬가지겠지? 라고 생각하던 시절이 저에게도… (나만인가?)

GIL

GIL에 대해서는 좋은 글이 있어서 블로그 링크로 대체하겠습니다. 왜 Python에는 GIL이 있는가

해당 글로 GIL에 대한 이해도가 더 깊어졌습니다. (김동건님 감사합니다!)

진실

GIL 때문에 하나의 쓰레드에서 하나의 파이썬 코드만 실행되는 것은 사실입니다. 해당 문제를 회피하기 위해서는 multiprocessing을 사용하거나(100% 완벽한 대안은 아닙니다.) 다른 라이브러리들을 사용해야 합니다. GIL이 걸릴 만한 코드를 간단히 살펴봅시다.

import asyncio

if __name__ == '__main__':
asyncio.run(main(100)) # or tons of thread number

async def main(threadCount: int):
    tasks = []
    for _ in range(threadCount):
        tasks.append(asyncio.create_task(nested()))
        await asyncio.gather(*tasks)

async def nested():
    # you writed here some awesome code with high cpu usage
    return

그리고 GIL이 걸리지 않을 만한 코드를 봅시다.

import asyncio

if __name__ == '__main__':
    asyncio.run(main(100)) # or tons of thread number

async def main(threadCount: int):
    tasks = []
    for _ in range(threadCount):
        tasks.append(asyncio.create_task(nested()))
        await asyncio.gather(*tasks)

async def nested():
    # you writed here some awesome code with IO
    return

뭐가 달라졌나요? 바로 작업이 CPU를 사용한다는 점과 IO를 사용한다는 점입니다. GIL은 현재 CPU를 점유하고 있을 때만 GIL을 걸고, CPU를 사용하지 않을 때는 GIL을 해제하여 다른 쓰레드에 작업을 넘겨줍니다. 따라서 http request를 받는 행위나 DB에 무언가 쓰거나 값을 가져오는 행위에 대해 Lock이 걸리지 않습니다. 실제로 제가 오해했던 “싱글 스레드” 로 동작하지 않는다는 뜻이죠. 좋습니다. 문제를 알았으니 해결법을 알아봅시다! 앞에서 말했듯이 multiprocessing을 사용하는 것이 조금 일반적인 방법이겠지만, 좀 더 간단히 라이브러리를 사용하고 싶다면 Celery를 사용하는 게 좋겠습니다. 이 부분은 이 글의 영역을 넘어가기에 다시 다른 블로그로 대체하겠습니다. 스포카 기술 블로그의 Celery를 이용한 긴 작업 처리 라는 글입니다. (감사합니다 스포카!)

TL:DR

CPython은 멀티쓰레드로 동작한다. 단지 GIL 때문에 CPU를 사용하는 작업에 대해서는 싱글쓰레드로 동작시키는 게 더 효율이 좋을 뿐.