Python 강좌 / 스레드·프로세스·asyncio의 차이와 선택 기준
스레드·프로세스·asyncio는 여러 작업을 진행하는 서로 다른 방법입니다. 작업이 CPU 계산에 시간을 쓰는지, 외부 응답을 기다리는지와 사용하는 라이브러리의 지원 방식에 따라 선택해야 합니다.
동시 진행과 병렬 실행 구분하기
| 방법 | 특징 | 확인할 비용 |
|---|---|---|
| 스레드 | 같은 프로세스의 메모리를 공유 | 공유 상태 동기화·라이브러리 동작 |
| 프로세스 | 별도 메모리 공간에서 실행 | 시작·직렬화·데이터 전달 비용 |
| asyncio | 협력적으로 대기 지점을 넘기며 진행 | 비동기 라이브러리·이벤트 루프 차단 |
일반적인 GIL 활성 CPython에서는 순수 Python CPU 계산이 스레드를 늘린다고 곧바로 병렬 가속되는 것은 아닙니다. GIL 해제 확장이나 free-threaded 빌드 등 실행 환경에 따라 조건이 달라지므로 구현·빌드·라이브러리를 함께 확인합니다.
관련 동작과 지원 조건은 스레드·프로세스·asyncio의 차이와 선택 기준 관련 공식 문서에서 확인할 수 있습니다.
전체 예제와 실행 결과
별도의 실습 폴더에서 아래 파일을 UTF-8로 저장합니다. 가상환경을 선택한 터미널에서 python concurrency.py 명령으로 실행합니다. macOS·Ubuntu에서 가상환경을 사용하지 않는 경우 명령 이름을 python3으로 바꿉니다.
concurrency.py 파일의 전체 내용입니다.
import asyncio
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
def square(value):
return value * value
async def async_square(value):
await asyncio.sleep(0.01)
return square(value)
async def gather_values():
return await asyncio.gather(*(async_square(x) for x in [1, 2, 3]))
def main():
with ThreadPoolExecutor(max_workers=2) as pool:
print(list(pool.map(square, [1, 2, 3])))
with ProcessPoolExecutor(max_workers=2) as pool:
print(list(pool.map(square, [1, 2, 3])))
print(asyncio.run(gather_values()))
if __name__ == "__main__":
main()
예상 결과는 다음과 같습니다.
[1, 4, 9] [1, 4, 9] [1, 4, 9]
진입점·대기·실행 순서
Windows의 프로세스 생성 등에서는 새 프로세스가 모듈을 가져오므로 main 보호가 중요합니다. 작업 함수 square를 모듈 최상위에 정의해 프로세스가 전달받아 실행할 수 있게 했습니다. 노트북이나 대화형 환경에서는 프로세스 실행 조건이 다를 수 있으므로 .py 파일로 실행합니다.
async 함수도 await 없이 CPU 계산이나 동기 네트워크 호출을 오래 실행하면 이벤트 루프를 막을 수 있습니다. asyncio.sleep은 대기 시간을 다른 작업에 양보하는 예제이며 실제 HTTP 요청을 수행하는 것은 아닙니다.
성능과 오류를 따로 확인하기
이 작은 제곱 예제는 API 차이를 보여 주며 성능 비교 자료가 아닙니다. 작은 작업은 프로세스 생성 비용 때문에 오히려 느릴 수 있습니다. 실제 데이터·환경에서 시간을 측정하고 작업 수·동시 실행 수·메모리를 함께 기록합니다.
map은 입력 순서에 맞춘 결과를 제공하지만 작업 완료 순서는 다를 수 있습니다. 공유 리스트를 여러 스레드에서 바꾸거나 무제한 작업을 생성하지 말고 동시성 제한·오류 전파·취소·종료 처리를 설계합니다. asyncio.run은 이미 실행 중인 이벤트 루프 안에서 다시 호출할 수 없다는 점도 확인하세요.




