본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] threading vs asyncio : 동시성 프로그래밍 완전 비교

by Papa Martino V 2025. 7. 25.
728x90

threading vs asyncio : 동시성 프로그래밍 완전 비교
[PYTHON] threading vs asyncio

 

Python에서 동시성(concurrency)을 구현하는 대표적인 방법으로는 threadingasyncio가 있다. 두 방식은 모두 동시에 여러 작업을 처리하는 데 사용되지만, 내부 메커니즘과 적용 대상이 크게 다르다. 이 글에서는 threading과 asyncio의 동작 원리, 사용 예시, 성능 차이, 실무 적용 사례를 비교 분석하여 개발자가 프로젝트에 맞는 방식을 선택할 수 있도록 안내한다.


1. Python의 동시성 모델 이해하기

  • 멀티스레딩 (threading): 하나의 프로세스에서 여러 스레드를 생성해 병렬 작업
  • 비동기 프로그래밍 (asyncio): 이벤트 루프 기반 코루틴 처리

두 모델 모두 CPU가 아닌 I/O 병목을 줄이는 데 적합하다. 하지만 Global Interpreter Lock(GIL)이라는 Python의 고유 구조 때문에 스레드가 병렬적으로 실행되는 것처럼 보여도 실제로는 한 번에 하나의 스레드만 Python 바이트코드를 실행할 수 있다. 이는 동시성 모델 선택에 중요한 영향을 미친다.


2. threading - 고전적 방식의 멀티스레딩

threading 모듈은 Python의 표준 라이브러리로, 개발자가 간단하게 스레드를 생성하고 실행할 수 있도록 지원한다.

import threading
import time

def worker(name):
    print(f"{name} 작업 시작")
    time.sleep(2)
    print(f"{name} 작업 완료")

thread1 = threading.Thread(target=worker, args=("A",))
thread2 = threading.Thread(target=worker, args=("B",))

thread1.start()
thread2.start()

thread1.join()
thread2.join()
  • 실행 중인 작업이 블로킹(blocking) 함수인 경우 유용
  • 스레드 간 자원 공유가 가능하지만, race condition 발생 가능성 존재
  • 스레드가 많아질수록 컨텍스트 스위칭 비용 증가

3. asyncio - 비동기 함수와 코루틴의 힘

asyncio이벤트 루프 기반의 비동기 프로그래밍 방식이다. 하나의 스레드에서 수많은 작업을 await 키워드로 잠시 멈추고 다른 작업을 수행하는 식으로 처리한다.

import asyncio

async def worker(name):
    print(f"{name} 작업 시작")
    await asyncio.sleep(2)
    print(f"{name} 작업 완료")

async def main():
    await asyncio.gather(
        worker("A"),
        worker("B")
    )

asyncio.run(main())
  • 코드가 논리적으로 직관적이며 가독성이 좋음
  • I/O 바운드 작업에 이상적 (예: 네트워크 요청, 파일 처리)
  • 스레드보다 메모리 사용이 적고 속도 빠름

4. asyncio vs threading 비교표

비교 항목 threading asyncio
기반 구조 스레드 (Thread) 코루틴 (Coroutine)
실행 방식 병렬적인 흐름 (컨텍스트 스위칭) 이벤트 루프를 통한 협력형 동시성
적합한 작업 블로킹 작업 (I/O, 네트워크, DB) 논블로킹 I/O, 수많은 경량 작업
자원 사용량 상대적으로 많음 매우 적음
코드 복잡도 낮음 초기 진입장벽 높음 (await 등)
GIL 영향 영향 있음 영향 없음 (단일 스레드 기반)

5. 실무 적용 사례

도메인 추천 방식 설명
웹 크롤링 asyncio 수많은 URL 요청을 병렬로 처리
파일 업로드 처리 threading 멀티파트 처리와 파일 저장 시 적합
데이터베이스 요청 처리 asyncio + 비동기 드라이버 asyncpg, aiomysql 등의 도구와 조합
멀티 CPU 연산 멀티프로세싱 threading, asyncio 모두 한계 있음

6. asyncio의 주요 구성 요소

  • async def - 코루틴 선언
  • await - 다른 코루틴을 기다림
  • asyncio.gather() - 여러 작업 동시 실행
  • asyncio.run() - 이벤트 루프 실행

Python 3.7 이후부터는 asyncio.run()을 기본 실행 방식으로 권장한다.


7. 주의할 점

  • threading은 race condition을 막기 위해 Lock, Semaphore 등을 잘 사용해야 함
  • asyncio는 블로킹 코드를 사용하면 전체 루프가 멈추므로 비동기 라이브러리만 사용할 것
  • 동시성 모델을 혼합하면 오히려 복잡성 증가

8. 결론: 어떤 방식을 선택해야 할까?

Python의 동시성 처리는 간단한 개념이지만, 실제로는 선택의 폭이 넓다. 다음 기준을 통해 방향을 잡자.

  • I/O 작업이 많고 요청이 많다면 → asyncio
  • 단순한 병렬 처리 또는 외부 라이브러리 연동 필요 → threading
  • CPU 연산이 많다면 → multiprocessing

Python의 동시성은 GIL이라는 제약을 안고 있지만, 상황에 맞는 전략을 잘 선택하면 충분한 성능과 생산성을 얻을 수 있다. 두 방식 모두 익혀두면 어떤 아키텍처에서도 유연하게 대응할 수 있다.


출처 (References)

728x90