본문으로 건너뛰기
개발 머꼬
개발 노트Python
hohyeon.dev19

Python에서 스레드를 늘렸는데 CPU 작업이 빨라지지 않는 이유

  • #Concurrency
  • #Engineering Note
  • #GIL
  • #Python

문제 발생

이미지 리사이징처럼 CPU를 많이 쓰는 작업을 threading 모듈로 4개 스레드에 나눠 돌렸는데, 코어가 4개인데도 속도가 거의 늘지 않았습니다.

import threading

threads = [threading.Thread(target=cpu_heavy_task) for _ in range(4)]
for t in threads:
    t.start()
# 4배 빨라질 것으로 기대했지만 거의 그대로

원인 분석

CPython(가장 널리 쓰이는 Python 구현체)에는 **GIL(Global Interpreter Lock)**이 있습니다. GIL은 한 프로세스 안에서 한 번에 하나의 스레드만 Python bytecode를 실행하도록 강제하는 락입니다. 즉 스레드를 아무리 늘려도 CPU 연산 자체는 한 번에 한 스레드씩만 실제로 실행됩니다.

GIL이 있음에도 threading이 유용한 경우는 I/O 대기(네트워크 요청, 파일 읽기, DB 쿼리 대기 등)입니다. I/O 대기 중에는 GIL이 해제되어 다른 스레드가 실행될 수 있습니다. 그래서 웹 요청을 여러 개 동시에 보내는 작업은 threading으로 실제 이득을 봅니다. 반면 순수 계산(이미지 처리, 암호화, 수치 연산)은 GIL 때문에 스레드를 늘려도 병렬로 실행되지 않습니다.

해결 방안

  1. I/O 위주 작업(네트워크, 파일, DB)은 threading이나 asyncio로 동시성을 얻습니다. GIL이 병목이 아닙니다.
  2. CPU 위주 작업(계산, 이미지/데이터 처리)은 multiprocessing을 씁니다. multiprocessing은 스레드가 아니라 별도 프로세스를 만들기 때문에 각 프로세스가 자기만의 Python 인터프리터와 GIL을 가져 진짜 병렬 실행이 됩니다.
from multiprocessing import Pool

with Pool(processes=4) as pool:
    results = pool.map(cpu_heavy_task, data_chunks)
  1. multiprocessing은 프로세스 간 데이터 전달에 직렬화(pickle) 비용이 들고 메모리를 공유하지 않으므로, 작업 단위가 너무 작으면 프로세스 생성/통신 오버헤드가 이득보다 커질 수 있습니다.
  2. NumPy, Pandas 같은 라이브러리의 일부 연산은 내부적으로 C로 구현되어 실행 중 GIL을 해제하므로, 순수 Python 반복문보다 이런 라이브러리를 쓰는 것 자체가 GIL 문제를 우회하는 방법이 되기도 합니다.
  3. Python 3.13부터 실험적으로 GIL을 비활성화할 수 있는 빌드(PEP 703, free-threaded build)가 제공되기 시작했지만 아직 기본값이 아니며 생태계 호환성이 안정화되는 중입니다 — 프로덕션 적용 전 실제 사용 중인 Python 버전과 배포 상태를 확인해야 합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.