
Python에서 동시성(concurrency)을 구현하는 대표적인 방법으로는 threading과 asyncio가 있다. 두 방식은 모두 동시에 여러 작업을 처리하는 데 사용되지만, 내부 메커니즘과 적용 대상이 크게 다르다. 이 글에서는 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)
'Artificial Intelligence > 60. Python' 카테고리의 다른 글
| [PYTHON] Cython으로 파이썬 속도 10배 빠르게 만들기 (0) | 2025.07.25 |
|---|---|
| [PYTHON] tkinter로 만드는 파이썬 GUI : 윈도우부터 이벤트까지 (0) | 2025.07.25 |
| [PYTHON] setup.py vs pyproject.toml : Python 패키징의 과거와 미래 (0) | 2025.07.25 |
| [PYTHON] AWS Lambda로 서버리스 Python 애플리케이션 구축하기 (0) | 2025.07.25 |
| [PYTHON] Python 프로젝트에 Docker 적용하기: 개발과 배포를 혁신하는 방법 (0) | 2025.07.25 |