비동기 I/O(Asynchronous I/O)는 I/O 작업 완료를 기다리지 않고 다른 작업을 계속 수행하는 프로그래밍 모델이다. 스레드 풀로 동시성을 확보하는 대신, 이벤트 루프와 콜백/코루틴으로 단일 스레드에서 수천 개의 I/O를 처리한다.
동기 vs 비동기 I/O
동기 (Blocking):
Thread: Read() ──── 대기 ────── 완료 → 다음 작업
I/O: ▓▓▓▓▓▓▓▓▓▓
비동기 (Non-blocking):
Thread: Read() → 다음 작업1 → 다음 작업2 → I/O완료 콜백
I/O: ▓▓▓▓▓▓▓▓▓▓ (백그라운드)
I/O 모델 분류 (POSIX)
| 모델 | 대기 방식 | 완료 통보 |
|---|
| Blocking I/O | 완료까지 블록 | 반환값 |
| Non-blocking I/O | 즉시 반환 (EAGAIN) | 폴링 |
| I/O Multiplexing | select/poll/epoll | fd ready |
| Signal-driven | 즉시 반환 | SIGIO |
| Async I/O (aio_*) | 즉시 반환 | 콜백/시그널 |
python
import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
urls = [f"https://httpbin.org/delay/1" for _ in range(10)]
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
results = await asyncio.gather(*tasks) # 동시 실행
# 10개 요청 ~1초 완료 (순차: 10초)
asyncio.run(main())
epoll (Linux)
c
// 수천 개 fd를 효율적으로 모니터링 (O(1))
int epfd = epoll_create1(0);
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
while (true) {
int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
for (int i = 0; i < n; i++) {
// 준비된 이벤트 처리
handle_event(events[i]);
}
}
Node.js 이벤트 루프
Node.js는 libuv의 이벤트 루프 + 비동기 I/O로 단일 스레드에서 수만 건의 동시 요청을 처리한다.
관련 개념
- •동시성 — 비동기 I/O가 구현하는 목표
- •이벤트 루프 — 비동기 I/O의 핵심 메커니즘
- •코루틴 — async/await의 기반
- •스레드 풀 — 동시성을 위한 대안 방식