먼저 축(Axis)을 분리하자
블로킹 vs 논블로킹 (호출 반환 시점)
| 구분 | 블로킹(Blocking) | 논블로킹(Non-blocking) |
|---|
| 핵심 질문 | "호출한 스레드가 기다리는가?" | "호출이 즉시 돌아오는가?" |
| 반환 시점 | 작업 가능 상태가 될 때까지 대기 | 즉시 반환 |
| 대표 예시 | recv() (기본 소켓) | 논블로킹 소켓의 recv() |
| 흔한 결과 코드 | 대기 후 성공/실패 | 준비 전이면 WSAEWOULDBLOCK |
동기 vs 비동기 (완료 통지 시점)
| 구분 | 동기(Synchronous) | 비동기(Asynchronous) |
|---|
| 핵심 질문 | "반환 시점에 작업이 끝났는가?" | "완료를 나중에 통지받는가?" |
| 완료 확인 방식 | 호출 결과를 즉시 확인 | 이벤트/콜백/완료 큐에서 나중 확인 |
| 대표 예시 | recv 반환값으로 결과 확인 | WSARecv(Overlapped) + 완료 통지 |
핵심 결론
- 블로킹/논블로킹과 동기/비동기는 서로 다른 축입니다.
- 따라서 "논블로킹이면 자동으로 비동기"가 아닙니다.
2x2 매트릭스로 정리
| 동기(Sync) | 비동기(Async) |
|---|
| 블로킹 | 일반 recv/send (대기 후 완료) | 개념적으로 가능하나 일반 Winsock 실무에서는 거의 사용되지 않음 |
| 논블로킹 | recv 즉시 반환 + WSAEWOULDBLOCK 재시도 패턴 | Overlapped/IOCP: 요청 후 나중에 완료 통지 |
실무에서 자주 쓰는 3가지
- 블로킹 + 동기: 가장 단순, 소규모/학습용
- 논블로킹 + 동기: 이벤트 루프 + 재시도(폴링/준비확인)
- 논블로킹 + 비동기: 고성능 서버(Overlapped, IOCP)
타임라인 감각
sequenceDiagram
participant T as Thread
participant K as Kernel
rect rgb(245, 245, 245)
Note over T,K: Blocking + Sync (recv)
T->>K: recv 호출
K-->>T: 데이터 준비 시 반환(완료)
end
rect rgb(245, 250, 255)
Note over T,K: Non-blocking + Sync (recv)
T->>K: recv 호출
K-->>T: 즉시 반환(WSAEWOULDBLOCK 가능)
T->>K: 나중에 다시 recv 호출
K-->>T: 준비 시 반환(완료)
end
rect rgb(245, 255, 245)
Note over T,K: Non-blocking + Async (WSARecv Overlapped)
T->>K: WSARecv 요청 등록
K-->>T: 즉시 반환(WSA_IO_PENDING 가능)
K-->>T: 나중에 완료 통지(Event/IOCP)
end
WSAEWOULDBLOCK vs WSA_IO_PENDING
둘을 섞어 이해하면 안 된다
| 항목 | WSAEWOULDBLOCK | WSA_IO_PENDING |
|---|
| 주로 나오는 맥락 | 논블로킹 recv/send/accept | Overlapped 비동기 요청 |
| 의미 | "지금은 준비 안 됨, 나중에 다시 시도" | "요청 등록됨, 완료는 나중에 통지" |
| 코드 패턴 | 재호출/준비 이벤트 감시 | 완료 이벤트/IOCP 대기 |
| 성격 | 즉시 완료 실패(재시도 필요) | 비동기 진행 중(정상 경로) |
최소 코드 비교
int ret = recv(sock, buf, len, 0);
if (ret == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK) {
}
DWORD flags = 0, bytes = 0;
int r = WSARecv(sock, &wsaBuf, 1, &bytes, &flags, &ov, nullptr);
if (r == SOCKET_ERROR && WSAGetLastError() == WSA_IO_PENDING) {
}
비동기 != 멀티스레드
관점 분리
- 비동기는 "완료를 언제 알리느냐"의 문제입니다.
- 멀티스레드는 "어느 스레드가 일을 하느냐"의 문제입니다.
예시
- 단일 스레드 이벤트 루프도 비동기 구조를 만들 수 있습니다.
- IOCP는 비동기 I/O + 워커 스레드 풀을 결합한 고성능 모델입니다.
설계 판단 기준
- 동접이 작고 단순하면 동기 모델로도 충분
- 동접/트래픽이 커지면 비동기 완료 기반 모델이 유리
- 스레드 수를 늘리기 전에, 대기 방식(블로킹/비동기)을 먼저 점검
강의 시 유의사항
강조 포인트
- 이 파트의 목적은 "용어 암기"가 아니라 "축 분리 사고"입니다.
- 다음 Part 17(Overlapped)과 Part 18(IOCP)을 이해하려면 이 구분이 선행되어야 합니다.
WSAEWOULDBLOCK과 WSA_IO_PENDING의 의미 차이를 반드시 비교해 설명하세요.
자주 하는 오해
| 오해 | 바로잡기 |
|---|
| 논블로킹이면 비동기다 | 논블로킹 + 동기 모델도 흔하다 |
| 비동기는 반드시 멀티스레드다 | 단일 스레드 이벤트 루프도 비동기 가능 |
WSA_IO_PENDING은 에러다 | 비동기 요청이 정상 등록됐다는 신호 |
체크 질문 (스스로 답해보기)
- 왜 블로킹/논블로킹과 동기/비동기를 하나의 축으로 보면 안 되는가?
WSAEWOULDBLOCK과 WSA_IO_PENDING을 각각 어떤 상황에서 처리해야 하는가?
- 내 서버가 동접 1만 이상으로 증가할 때 어떤 조합으로 전환할지 기준을 세워보라.