혼동하기 쉬운 블록킹/논블록킹과 동기/비동기 개념을 정리합니다.
개념 정의
블록킹 vs 논블록킹
구분 기준: 제어권을 바로 돌려주는가?
- 블록킹: 작업 완료까지 제어권을 돌려주지 않음
- 논블록킹: 작업 요청 후 바로 제어권 반환
카페 주문 비유:
- 블록킹: 카운터에서 서서 기다림 (다른 일 못함)
- 논블록킹: 진동벨 받고 자리로 (다른 일 가능)
동기 vs 비동기
구분 기준: 결과를 누가 확인하는가?
- 동기: 요청한 쪽이 결과를 직접 확인
- 비동기: 결과가 준비되면 콜백으로 알려줌
카페 주문 비유:
- 동기: 내가 직접 “제 커피 됐나요?” 확인
- 비동기: 직원이 “OO번 고객님~” 불러줌
핵심 구분
| 구분 | 질문 | 블록킹 | 논블록킹 |
|---|---|---|---|
| 제어권 | 기다리는 동안 다른 일 할 수 있어? | 못함 (멈춤) | 가능 (안 멈춤) |
| 구분 | 질문 | 동기 | 비동기 |
|---|---|---|---|
| 결과 확인 | 결과를 내가 확인해? 알려줘? | 내가 확인 | 알려줌 |
C 디바이스 I/O 예제
블록킹 I/O
int fd = open("/dev/device", O_RDONLY);
char buf[1024];
// 데이터 올 때까지 멈춤
int result = read(fd, buf, 1024);
// 여기 도달 = buf 채워짐 보장
printf("읽음: %s", buf);
동작:
- 프로세스가 커널에 read() 요청
- 데이터 없으면 프로세스 sleep
- 데이터 도착하면 커널이 프로세스 깨움
- 그제서야 다음 줄 실행
논블록킹 I/O + Polling
int fd = open("/dev/device", O_RDONLY | O_NONBLOCK);
char buf[1024];
while (1) {
int result = read(fd, buf, 1024); // 바로 리턴
if (result > 0) {
printf("읽음: %s", buf);
break;
} else if (result == -1 && errno == EAGAIN) {
// 데이터 없음 → 다른 일 하고 다시 확인
doOtherWork();
}
}
동작:
- 프로세스가 커널에 read() 요청
- 데이터 없으면 즉시 -1 리턴
- 프로세스는 계속 실행
- 반복해서 확인 (polling)
논블록킹 I/O + select()
int fd = open("/dev/device", O_RDONLY | O_NONBLOCK);
fd_set readfds;
while (1) {
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
// 타임아웃 100ms
struct timeval timeout = {0, 100000};
int ready = select(fd + 1, &readfds, NULL, NULL, &timeout);
if (ready > 0) {
read(fd, buf, 1024);
break;
} else if (ready == 0) {
doOtherWork();
}
}
동작:
- select()로 데이터 준비 여부를 커널에 위임
- 효율적인 대기 - CPU가 계속 확인하는 대신 커널이 깨워줄 때까지 대기
- 타임아웃 시 다른 작업 가능
Java Spring 예제
동기 + 블록킹 (RestTemplate)
@GetMapping("/user")
public User getUser() {
// 현재 스레드가 여기서 멈춤
User user = restTemplate.getForObject(url, User.class);
return user;
}
비동기 + 논블록킹 (WebClient)
@GetMapping("/user")
public Mono getUser() {
// 요청만 하고 바로 반환
return webClient.get()
.uri(url)
.retrieve()
.bodyToMono(User.class);
}
I/O 방식별 비교
| 방식 | 대기 | 효율 | 사용 환경 |
|---|---|---|---|
| 블록킹 | 스레드 멈춤 | 단순함 | 일반 애플리케이션 |
| 논블록킹 + Polling | 안 멈춤 (반복 체크) | CPU 낭비 | 임베디드 |
| 논블록킹 + select/epoll | 안 멈춤 (이벤트 대기) | 높음 | 서버 |
조합별 특징
| 조합 | 빈도 | 예시 |
|---|---|---|
| 동기 + 블록킹 | ✅ 흔함 | JDBC, RestTemplate |
| 비동기 + 논블록킹 | ✅ 흔함 | WebFlux, Node.js |
| 동기 + 논블록킹 | 드묾 | polling |
| 비동기 + 블록킹 | 드묾 | 안티패턴 |
정리
- 블록킹/논블록킹: 제어권 반환 시점
- 동기/비동기: 결과 확인 주체
- 대부분 동기+블록킹 또는 비동기+논블록킹 조합 사용
- I/O 방식 선택은 성능과 복잡도의 트레이드오프
참고 비동기 + 블록킹 안티패턴
@GetMapping("/user")
public User getUser() {
// 비동기로 시작했지만...
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
return userService.findUser();
});
// get()으로 블록킹하면 비동기의 의미 없음
return future.get(); // 여기서 블록킹!
}
비동기로 작업을 시작했지만 결과를 기다리느라 블록킹되어 비동기의 장점이 사라짐.