Asyncio 비동기 프로그래밍, 파이썬에선 어떻게 작동할까?(동기 vs 비동기)
느린 I/O에 발목 잡히는 파이썬 코드, 비동기로 풀 수 있을까요? asyncio가 답일 수 있습니다!
안녕하세요, 개발자 여러분! ICT리더 리치입니다.
파이썬에서 네트워크나 파일 처리처럼 시간이 오래 걸리는 작업을 효율적으로 처리하고 싶으셨던 적 있으신가요? 저도 백엔드 API 서버를 개발하면서 느린 응답 때문에 스트레스를 받았었는데요. 그러다 만난 것이 바로 asyncio였습니다.
오늘은 초보자도 이해할 수 있도록 asyncio가 어떻게 작동하는지, 그리고 실제로 어떻게 활용할 수 있는지를 쉽고 명확하게 설명해드릴게요. 본 포스팅을 통해 여러분도 비동기 마스터로 거듭나보세요!
📌 바로가기 목차
| 파이썬 asyncio 이해 중인 여성 개발자 - 텍스트 없는 대표 이미지 |
1. asyncio란 무엇인가요?
asyncio는 파이썬 표준 라이브러리에서 제공하는 비동기 프로그래밍 프레임워크입니다.
동시성 처리를 위해 이벤트 루프(Event Loop)를 기반으로 작동하며, CPU는 쉬지 않고 여러 작업을 동시에 처리할 수 있도록 도와줍니다.
멀티스레딩이나 멀티프로세싱과는 다르게, 하나의 스레드로 많은 작업을 효율적으로 처리할 수 있도록 설계되었습니다.
Python의 asyncio는 이벤트 루프를 기반으로 비동기 작업을 수행합니다. 아래는 가장 기본적인 asyncio 구조입니다.
import asyncio
# 비동기 함수 정의\async def greet():
print("Hello")
await asyncio.sleep(1)
print("World")
# asyncio 실행 함수
def run_async():
asyncio.run(greet())
run_async()
2. 동기 vs 비동기, 차이점은?
동기(Synchronous)와 비동기(Asynchronous)는 프로그램이 작업을 처리하는 방식의 차이를 말합니다. 아래의 테이블은 두 방식의 대표적인 차이점을 정리한 것입니다.
| 항목 | 동기(Sync) | 비동기(Async) |
|---|---|---|
| 작업 처리 | 순차적으로 처리 | 대기 없이 동시에 여러 작업 |
| CPU 활용 | 비효율적 (대기 시간 포함) | 효율적 (대기 중 다른 작업 수행) |
| 예시 | 파일 하나 처리 후 다음 파일 처리 | 여러 파일을 동시에 비동기 처리 |
여러 개의 비동기 작업을 동시에 실행하고 싶다면 asyncio.gather()를 사용할 수 있습니다.
import asyncio
async def task(name, delay):
print(f"Start: {name}")
await asyncio.sleep(delay)
print(f"End: {name}")
async def main():
await asyncio.gather(
task("Task 1", 2),
task("Task 2", 1),
task("Task 3", 3)
)
asyncio.run(main())
3. asyncio의 핵심 개념 정리
asyncio를 이해하기 위해 꼭 알아야 할 핵심 개념들을 정리해보았습니다.
- Event Loop: 작업들을 큐에 넣고, 하나씩 실행하는 핵심 스케줄러
-
Coroutine: 비동기 함수 정의 방식 (
async def) - await: 다른 coroutine을 호출하고 기다릴 때 사용하는 키워드
- Task: coroutine을 병렬적으로 실행하는 객체
실제 웹 요청에서 asyncio를 사용할 수 있습니다. 아래는 aiohttp를 사용하는 예시입니다.
import aiohttp
import asyncio
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
urls = [
'https://example.com',
'https://example.org',
'https://example.net'
]
async with aiohttp.ClientSession() as session:
results = await asyncio.gather(*[fetch(session, url) for url in urls])
for res in results:
print(res[:100])
asyncio.run(main())
![]() |
| Asyncio 비동기 프로그래밍 작동 원리 - 파이썬 개발자 인포그래픽 |
4. 실전 예제로 이해하는 asyncio
실제로 asyncio가 어떻게 작동하는지 간단한 코드 예제를 통해 살펴보겠습니다.
import asyncio
async def say_hello():
print("Hello")
await asyncio.sleep(1)
print("World")
async def main():
await asyncio.gather(say_hello(), say_hello())
asyncio.run(main())
위 코드는 say_hello() 함수를 두 번 동시에 실행하여 1초만에 두 번의 출력이 완료되는 비동기 구조입니다.
asyncio.gather()를 사용하면 여러 coroutine을 병렬로 처리할 수 있어 효율적인 구조를 만들 수 있습니다.
비동기 작업 중 특정 시간만큼 대기해야 할 경우 asyncio.sleep을 사용할 수 있습니다.
import asyncio
async def delayed_message():
print("3초 후 메시지 표시")
await asyncio.sleep(3)
print("✅ 메시지 도착!")
asyncio.run(delayed_message())
5. asyncio 사용할 때 흔한 실수
비동기를 처음 접하는 개발자들이 자주 겪는 실수들을 정리해보았습니다.
- 비동기 함수에
await없이 호출해서 실행되지 않는 문제 asyncio.run()을 중복 실행하여 이벤트 루프 충돌- 블로킹 함수(
time.sleep등)를 비동기 코드 안에서 사용 - 동기/비동기 코드 혼용으로 흐름 제어 혼란
async함수 내부에서 예외처리를 하지 않아 전역 오류 발생
Queue를 통해 생산자-소비자 패턴을 비동기로 구현할 수 있습니다.
import asyncio
async def producer(queue):
for i in range(5):
await queue.put(i)
print(f"Produced: {i}")
await asyncio.sleep(1)
async def consumer(queue):
while True:
item = await queue.get()
print(f"Consumed: {item}")
queue.task_done()
async def main():
queue = asyncio.Queue()
await asyncio.gather(producer(queue), consumer(queue))
asyncio.run(main())
6. asyncio가 적합한 상황은?
모든 상황에서 asyncio가 적합한 것은 아닙니다. 다음과 같은 경우에 사용할 때 특히 효과적입니다.
- 웹 크롤링 및 데이터 수집: 수많은 네트워크 요청을 동시에 처리할 때
- 실시간 채팅 시스템: 빠른 메시지 전달과 응답이 필요한 경우
- 비동기 API 서버: FastAPI, aiohttp 등과 함께 사용할 때
비동기 작업에서 특정 시간이 지나면 예외를 발생시켜 처리할 수 있습니다.
import asyncio
async def slow_task():
await asyncio.sleep(5)
return "완료!"
async def main():
try:
result = await asyncio.wait_for(slow_task(), timeout=3)
print(result)
except asyncio.TimeoutError:
print("⏰ 타임아웃 발생: 작업이 3초를 초과했습니다.")
asyncio.run(main())
![]() |
| Asyncio로 동시성 처리하는 파이썬 개발자 인포그래픽 |
7. 자주 묻는 질문 (FAQ)
threading은 실제로 여러 스레드를 생성하여 병렬처리를 수행하고, asyncio는 이벤트 루프를 활용하여 하나의 스레드 내에서 비동기로 작업을 처리합니다. CPU 작업이 많으면 threading, I/O 중심이면 asyncio가 적합합니다.
네, async 키워드로 정의한 함수는 coroutine이기 때문에, 호출 시 await 키워드로 호출 결과를 기다려야 합니다. 그렇지 않으면 실행되지 않거나 오류가 발생할 수 있습니다.
asyncio.sleep()은 비동기적으로 대기 시간을 주는 함수입니다. 일반 sleep처럼 CPU를 점유하지 않고, 그 시간 동안 다른 작업이 수행될 수 있도록 합니다.
꼭 그렇지는 않습니다. 네트워크 요청, 파일 읽기/쓰기 등 I/O가 많은 작업에서만 비동기를 적용해도 충분한 성능 향상을 기대할 수 있습니다.
FastAPI는 기본적으로 asyncio를 기반으로 작동합니다. 따라서 API 핸들러는 async로 작성해야 최상의 성능과 효율을 낼 수 있습니다.
8. 마무리 요약
✅ asyncio는 파이썬의 현대적인 비동기 해답입니다
동시성 문제로 고민 중이라면, 이제는 asyncio를 활용해보세요.
단순한 코드 변경만으로도 네트워크 요청, 파일 I/O 같은 작업을 효율적으로 처리할 수 있습니다.
특히 웹 크롤링, 실시간 응답이 필요한 시스템에서 그 진가를 발휘합니다.
비동기 코드를 정확히 이해하고 실전 예제를 통해 체화한다면,
더 빠르고 가볍고 유연한 파이썬 프로젝트를 구현할 수 있습니다.
오늘부터 asyncio를 직접 실습하며 체험해보세요!


댓글
댓글 쓰기