네트워크 I/O나 DB 접근을 공부하다보면 동기/비동기, 블로킹/논블로킹이라는 단어가 나오는데,
뭔가 희미하게 알고있지만 명확하게 구분하기 어렵고 똑 부러지게 설명할 자신이 없어서 정리해보기 위해 글을 작성한다.
동기 VS 비동기
동기/비동기는 호출자의 시선에서 작업의 완료를 어떻게 전달받을지 따지는 단어라고 생각한다. return의 순서를 신경쓰는가?에 초점을 두어야 한다.
"호출자가 호출한 작업이 끝났는지 직접 확인하는지"라고 생각하면 쉽게 이해할 수 있다.
동기: 호출자는 호출한 작업이 끝나는 것을 확인하고 다른 작업을 호출할 수 있다. 작업이 끝나는 것을 바로 직접 확인해야 한다.
비동기: 호출자는 호출한 작업이 끝나지 않아도 다른 작업을 호출할 수 있다. 작업이 끝나는 것을 나중에 알림으로 확인한다.
블로킹 VS 논블로킹
블로킹/논블로킹은 작업자의 시선에서 작업을 하는 방식을 따지는 단어라고 생각한다. Worker(스레드 등)의 제어권을 어디에 두는가?에 초점을 두어야 한다.
"하나의 작업자가 한 번에 여러 일을 수행할 수 있는지 여부"라고 생각하면 쉽게 이해할 수 있다.
블로킹: 한 명의 작업자가 한 번에 하나의 일만 수행 가능. 하나의 일이 끝날 때까지 거기에 묶임.
논블로킹: 한 명의 작업자가 여러 개의 일을 수행 가능. 작업자가 일이 끝나지 않아도 새로운 일을 할 수 있는 상태가 됨.
-> 동기/비동기는 순서, 블로킹/논블로킹은 효율 느낌으로 이해하면 편하다.
일어날 수 있는 상황
동기 + 블로킹: 호출자는 작업자를 시켜서 일을 시키는데 해당 작업자는 하나의 일만 할 수 있고 호출자도 작업자가 일이 끝날 때까지 직접 확인하는 상황
비동기 + 블로킹: 호출자는 작업자를 시켜서 일을 시키는데 일이 끝날 때까지 확인하지 않아도 돼서 여러 작업자한테 일을 시킬 수 있음. 하지만 한 작업자는 하나의 일만 할 수 있기 때문에 여러 일을 시키는데 많은 작업자가 필요한 상황
동기 + 논블로킹: 호출자는 작업자에게 시킨 일이 끝날 때까지 직접 확인한다. 하지만 작업자는 한 번에 여러 개의 일을 수행할 수 있는 상황.
비동기 + 논블로킹: 호출자는 작업자에게 시킨 일이 끝나는지 직접 확인하지 않아도 돼서 여러 작업자에게 일을 시킬 수 있고, 작업자들도 한 번에 여러 일을 수행할 수 있는 상황.
여기서 "한 번에 여러 일을 수행한다." 라는 말은 실제로 병렬로 처리된다는 것이 아니라 "제어권이 반환되어서 다른 작업을 시킬 수 있는 상태가 된다." 라는 뜻이다. 결국 한 명의 작업자이기 때문에 "여러 일을 왔다갔다하면서 처리할 수 있는 상태가 된다." 가 더 적합한 표현인 것 같다.
라면 끓이는 상황을 예시로 들어보면 라면을 끓이는 사람(블로킹/논블로킹), 라면을 주문한 사람(동기/비동기) 로 나누고
동기 + 블로킹은 라면 끓이는 사람이 라면이 다 끓을 때까지 앞에서 기다리고 주문한 사람도 라면이 나올 때까지 앞에서 기다린다.
비동기 + 블로킹은 진동벨이 생겨서 라면을 주문한 사람은 라면이 완성되면 진동벨로 완성 여부를 확인한다. 하지만 라면을 끓이는 사람은 여전히 하나의 라면을 다 끓일 때까지 앞에서 기다린다.
동기 + 논블로킹은 키오스크가 생겨서 라면을 끓이는 사람이 직접 주문을 받지 않아도 된다. 여러 라면을 끓일 수 있는데 물이 끓고 면/스프를 넣는 조리과정을 때가 되면 번갈아가면서 진행한다. 하지만 주문한 사람은 앞에서 라면이 나오는 것을 직접 확인해야한다.
비동기 + 논블로킹은 키오스크와 진동벨이 둘 다 있어서 라면을 주문한 사람도 기다리면서 다른 일을 할 수 있고 끓이는 사람도 여러 라면을 끓일 수 있다.
직접 코드를 확인해보자
비동기 + 논블로킹은 많이 접해본 setTimeOut 함수가 있다
function nonBlocking() {
console.log("start");
setTimeout(() => {
console.log("1 second after");
}, 1000);
console.log("end");
}
이런 함수가 있다고 해보자. nonblocking 함수는 setTimeout 함수를 호출했지만 해당 함수가 끝나는 것을 기다리지 않고 바로 다음 console.log 함수를 실행시킬 수 있다. 또한 setTimeout 함수가 nonBlocking 함수에 대한 제어권을 바로 반환하여 다음 함수를 실행시킬 수 있다.
어차피 다음 함수를 실행시키는건 똑같은데 뭐가 다른건가요? 라는 생각이 들 수 있지만 실제로 동작이 다르다.
1. 동기 + 블로킹
// 가정: blockingSleep(ms)는 지정 시간 동안 스레드를 멈춤
blockingSleep(1000); // ← 여기서 멈춤
console.log("이건 1초 뒤에 출력"); // 다음 줄은 기다렸다가 실행
쉽게 유추할 수 있겠지만 동기 + 블로킹 형식의 함수는 실행되면 해당 함수의 반환을 끝내고 다음 코드라인이 실행된다.
2. 동기 + 논블로킹
// 가정: setTimeoutSyncNonBlocking(ms) → 타임아웃이 지났는지 확인하는 check 함수를 반환
const check = setTimeoutSyncNonBlocking(1000); // 즉시 반환 (논블로킹)
console.log("이건 즉시 출력"); // 다음 줄 즉시 실행
const poll = setInterval(() => {
if (check()) { // 호출자가 직접 결과 확인(폴링)
console.log("타이머 끝!");
clearInterval(poll);
}
}, 100);
동기 + 논블로킹은 의아하게도 바로 다음 코드라인이 실행된다. 그 이유는 논블로킹이라 함수 호출이 즉시 반환되기 때문이다. 하지만 해당 함수가 끝났는지는 직접 확인해야한다.
3. 비동기 + 블로킹
// Java 예시: 비동기 인터페이스지만 내부는 블로킹 I/O
Future<String> f = executor.submit(() -> jdbcCall()); // 즉시 Future 반환 (비동기)
System.out.println("이건 즉시 출력");
String result = f.get(); // ← 여기서 블로킹됨(결과 받을 때 멈춤)
System.out.println("결과: " + result);
여기도 놀랍게도 코드 진행이 막히지 않는다.
동기 + 논블로킹의 이유와는 다르게 Future/promise를 바로 반환받기 때문이다. 근데 메인 함수 스레드가 해당 함수를 실행하면 스레드의 진행이 막히는거 아닌가요?? 라는 질문이 생겼다. 신기하게도 비동기 + 블로킹 방식은 호출된 함수가 다른 스레드에서 실행된다. 같은 스레드에서 실행되면 비동기가 깨지기 때문에 다른 워커를 불러서 해당 작업을 완료한다. 해당 함수를 실행하는 스레드만 블로킹의 형태로 진행된다.
직접 어떤 비유가 있을까 고민하다가 라면을 비유로 들었는데 적절한 예시인지는 모르겠습니다... 혹시나 작성된 내용 중에 틀린 부분, 정정해야할 부분이 있다면 댓글로 피드백 주시면 감사하겠습니다!
'Computer Science' 카테고리의 다른 글
| Algorithm - Tim Sort (0) | 2025.09.29 |
|---|---|
| HTTP - header2 (0) | 2025.08.15 |
| HTTP - header1 (0) | 2025.08.15 |
| HTTP - 상태 코드 (0) | 2025.08.15 |
| HTTP - 메서드 (0) | 2025.08.14 |