동기와 비동기는 작업의 완료와 결과를 처리하는 방식에 대한 개념이고, 블로킹과 논블로킹은 작업을 기다리는 동안 호출한 실행 흐름이 제어권을 가지고 있는지에 대한 개념입니다.
따라서 두 개념은 독립적인 축이며, @Async로 JDBC 작업을 별도 스레드에서 실행하는 것처럼 비동기이면서 내부적으로는 블로킹인 구조도 가능합니다.
- 동기/비동기: 요청한 작업에 대해 완료 여부를 신경 써서 작업을 순차적으로 수행할지 아닌지에 대한 관점
- 블로킹/논블록킹:현재 작업이 block(차단, 대기) 되느냐 아니냐에 따라 다른 작업을 수행할 수 있는지에 대한 관점
동기/비동기의 주요 관심사는 실행 순서와 결과 처리이다.
블로킹/논클로킹의 주요 관심사는 제어권이다.
| 구분 | 의미 | 대표 동작 | 사례 |
|---|---|---|---|
| Blocking | 결과나 이벤트를 기다리는 동안 호출한 스레드가 대기 | 데이터가 없는 소켓에서 blocking read()가 대기 | 파일 I/O, 소켓 I/O |
| Non-blocking | 지금 처리할 수 없으면 기다리지 않고 반환 | non-blocking read()가 EAGAIN/EWOULDBLOCK 반환 | 네트워크 서버, 이벤트 드리븐 시스템 |
| 동기 I/O | 호출 안에서 I/O를 수행하고 반환값으로 처리 결과를 받음 | read()가 실제 읽은 바이트 수를 반환 | 작업 간 의존성이 있는 경우 (트랜잭션) |
| 비동기 I/O | 요청을 제출한 뒤 호출과 분리하여 I/O가 진행되고 완료 결과를 확인 | AIO 요청 후 완료 알림이나 상태 조회로 결과 확인 | 네트워크 요청, 이벤트 기반 프로그래밍 |
예를 들어 non-blocking 소켓의 read()는 데이터가 있으면 읽은 결과를 반환하고, 없으면 재시도가 필요하다고 반환합니다. 반면 비동기 읽기는 요청이 접수되면 호출이 돌아온 뒤에도 읽기가 진행될 수 있습니다. 준비 상태 알림과 실제 I/O 완료 알림을 구분하는 것이 핵심입니다.
Java/Spring에서는 다음과 같이 사용됩니다.
| 조합 | 의미 | Java/Spring에서 생각할 예 |
|---|---|---|
| 동기 + Blocking | 결과를 직접 받고, 결과가 나올 때까지 스레드도 기다림 | JDBC/JPA, 일반 Spring MVC 코드 |
| 동기 + Non-Blocking | 직접 결과를 확인하지만 준비 안 됐으면 즉시 반환 | Java NIO SocketChannel |
| 비동기 + Blocking | 작업은 따로 실행하지만 결과를 기다리며 호출자가 Blocking | Future.get(), CompletableFuture.join() |
| 비동기 + Non-Blocking | 작업을 맡기고 호출자는 계속 실행, 완료되면 callback/event | Reactor, WebFlux, 비동기 I/O |
epoll은 비동기 I/O 완료를 알려주는 기능인가요?
epoll은 주로 소켓 등이 읽기·쓰기를 할 준비가 되었는지 알려주는 I/O 다중화 기능입니다. 애플리케이션이 직접 read()/write()를 호출해야 하며, epoll_wait() 자체는 이벤트를 기다리며 블로킹될 수 있습니다.
비동기 작업의 결과를 기다리면 블로킹이 될 수도 있나요?
가능합니다. 비동기로 제출한 작업이라도 완료 전에 Future.get() 같은 대기 API를 호출하면 그 스레드는 블로킹될 수 있습니다. 작업의 진행 방식과 결과를 기다리는 호출의 동작을 나눠 봐야 합니다.
Non-blocking I/O는 성공하면 요청한 데이터를 전부 처리하나요?
아닙니다. 소켓의 읽기·쓰기는 일부 바이트만 처리하고 반환할 수 있으므로 반환값만큼 진행한 뒤 나머지를 처리해야 합니다. 처리할 준비가 안 됐다면 무조건 반복 호출하기보다 준비 알림을 활용해 CPU 낭비를 줄일 수 있습니다.
Synchronous이면서 Non-blocking인 I/O의 예시는 무엇인가요?
O_NONBLOCK으로 설정한 소켓에서read()를 호출하는 경우가 대표적입니다. 준비된 데이터가 있으면 호출 안에서 직접 읽어 결과를 반환하고, 없으면 기다리지 않고EAGAIN또는EWOULDBLOCK을 반환하므로 Non-blocking입니다. 완료 결과를 별도 콜백으로 받는 비동기 I/O 요청이 아니라 애플리케이션이 준비 알림 등을 바탕으로 다시 호출하는 동기 I/O입니다.
출처 및 참고자료
- read(2) - Linux manual
- epoll(7) - Linux manual
- aio(7) - Linux manual
- Event-based Concurrency - OSTEP
- Future.get() - Java API