논 블로킹은 NodeJS의 대표적인 특성 중 하나이다. 그런데 보통 논 블로킹과 비동기는 비슷한 느낌으로 함께 쓰이는데, 둘의 차이가 무엇인지 이번에 명확히 짚어보도록 하자.
1. 작업의 분류
2. 블로킹 vs. 논 블로킹
3. 동기 vs. 비동기
4. 정리
1. 작업의 분류
컴퓨터가 수행하는 작업은 동시에 진행될 수 있는가를 기준으로 두 가지로 나눌 수 있다.
- 동시에 실행될 수 있는 작업
- 동시에 실행될 수 없는 작업
기본적으로 자바스크립트 코드는 동시에 실행될 수 없다. 예를 들어
var x = 3;
var y = 5;
간단한 변수 선언인데, 이 변수 선언을 하는 첫번째 줄과 두번째 줄이 동시에 실행될 수 있나? 위와 같이 순차적으로 쓸 경우 그럴 수 없다.
하지만 자바스크립트 상에서 돌아가는 것이 아닌(CPU가 작업하는 게 아니라) I/O 작업의 경우는 동시에 처리가 가능하다. I/O는 입출력을 말한다. 파일 시스템 접근(파일 읽기, 쓰기, 폴더 만들기 등), 네트워크를 통한 요청 등이 I/O 작업에 해당한다. 이런 I/O 작업을 할 때 노드는 논 블로킹 방식으로 처리하는 방법을 제공한다.
2. 논 블로킹 & 블로킹
일반적으로 논 블로킹이 더 많이 쓰이는 개념인 것 같으나, 이를 이해하려면 개인적으로 블로킹 자체를 이해하는 게 더 중요하다고 판단된다. 예시를 보자.
function longRunningTask() {
// 오래 걸리는 작업
console.log('작업 끝');
}
console.log('시작');
longRunningTask();
console.log('다음 작업');
이를 실행하면 결과는 아래와 같다.
시작
작업 끝
다음 작업
여기서 함수 이름과 같이 longRunningTask()를 보면, 오래 걸리는 I/O 작업에 해당한다. 이 코드가 실행되는 동안 아래 있는 코드는 가만히 기다려야 하니, 위에 있는 코드는 자기를 실행하느라 아래 코드를 실행하는 것을 막게(blocking) 된다. 이렇게 뒤에 실행될 수 있는 코드를 막는 행위를 블로킹이라고 한다.
그러면 뭔가 이상하다. 모든 코드가 다 아래 코드의 블로킹인 셈인 거 아니냐?라고 볼 여지가 있다. 자바스크립트 코드는 기본적으로 동시에 실행되지 않기 때문이다. 여기서 주의할 점은, 단순히 시간이 오래 걸려 다음 코드 실행을 막는다고 블로킹이 아니라는 점이다. cpu를 많이 잡아먹어 속도가 느려지는 코드더라도 이는 블로킹에 해당하지 않는다. 기본적으로 자바스크립트는 동시에 실행되지 않기 때문이다. I/O와 같이 자바스크립트 바깥에서 실행 가능한 작업을 기다리느라 자바스크립트 코드가 멈춰있는 상황을 블로킹이라고 하는 것임을 명심하자.
그러면 논 블로킹은 개념이 명확해진다. I/O 작업과 같이 자바스크립트 바깥에서 이뤄지는 작업이 끝날 때까지 기다리는 게 아니라 해당 작업을 실행시키고 나면 그 작업이 완료될 때까지 기다리지 않고 곧바로 다음 작업을 수행하는 방식이다. 이를 자바스크립트에서는 "제어권을 곧바로 갖는다"고 말한다.
즉, 블로킹/논 블로킹은 요청자 함수가 응답자 함수에게 제어권을 주냐, 아니면 제어권을 줬다가 곧바로 다시 회수하냐와 같이 작업으로서의 관점에 해당한다. 일을 시키고 나서 다시 자기 할 일을 하냐, 아니면 시킨 일이 다 될 때까지 기다리냐의 차이인 것.
여기서 또 주의할 점은, 논 블로킹 자체가 동시성인 것은 아니라는 점. 예를 들어 요청하는 함수와 요청받는 함수가 동시에 이뤄질 수 있는 작업에 해당하면 이는 병렬로 진행 가능하다.(ex-자바스크립트 코드 & I/O 요청) 그런데 둘다 자바스크립트 코드에 해당하는데 논 블로킹 방식을 적용할 수 있다. 이렇게 짠다고 동시 작업이 불가능한 애들을 동시에 작업할 수 있는 것은 아니다. 논 블로킹은 그냥 제어권을 줬다가 곧바로 회수해오는 개념이다. 만약 동시에 작업할 수 없다면 제어권을 줬다가 곧바로 회수해 자기 작업만 시행한 뒤, 끝나고 cpu 제어권을 넘겨주는 방식으로 진행될 것이다. 즉, 동시성은 기본적으로 동시 처리가 가능한 작업에 해당되어야만 한다.
3. 동기 vs 비동기
위의 블로킹/논 블로킹이 제어권을 누가 갖냐에 대한 작업 관점이라면, 동기/비동기는 작업을 실행하는 여러 함수들이 "시간에 맞춰 실행되는지"에 대한 시간 관점이다.
먼저 동기(synchronous)란 무엇인지 보자. 동기는 함수들이 시간을 맞춰 실행되는 방식이다. 시간을 맞춘다는 건 무엇인가? 두 개 이상의 함수가 존재할 때,
이 함수들이 작업을 동시에 시작하거나,
끝나는 타이밍을 맞추거나,
혹은 하나가 끝나고 다른 하나를 차례로 실행하거나
이를 시간을 맞춘다고 한다(함수들이 함께 한다는 측면에서 동기)
이를 어떻게 알 수 있을까? 사실 이 개념이 더 중요하다고 보는데, 요청자는 자신이 요청한 일들에 대해 응답자 함수가 일을 끝냈는지 계속해서 체크하기 때문에 확인이 가능하다. 사실상 동기를 이 개념으로 이해하는 게 더 좋다고 본다. 즉, 동기는 요청자 함수는 요청을 보내놓고 요청을 처리하는 함수들이 작업을 마쳤는지 계속해서 물어보는 방식. 요청자는 응답자가 작업을 완료했다는 응답을 받으면 그때 다른 함수의 실행을 시작하거나 한다.
그러면 비동기(Asynchronous)는 간단하다. 동기적이지 않은 방식이다. 요청자 함수는 응답자 함수가 언제 일을 시작하고 마치는지에 대해 전혀 체크하지 않는다. 요청자가 응답자 작업 상태를 계속 점검하는 게 아니라, 응답자가 요청자한테 자기 작업을 완료했다고 알려주는 방식이다. 작업을 시작하고 끝내는 시간을 아는 것은 호출된 함수 자신만이기에 여러 함수들이 시작하고 끝나는 시간이 맞춰지지(동기) 않는다. 결과적으로 결과값과 제어권이 각각 다른 시점에 따로따로 반환된다.
정리하면, 동기는 요청 작업이 끝났는지 요청자가 계속 물어보고 그 시간에 맞춰서 다른 작업을 수행하는 방식이다. 비동기는 작업해달라는 요청만 해놓고 더이상 신경쓰지 않다가 상대방이 끝나면 자기가 끝났다고 알아서 말해주는 방식이다.
4. 정리
블로킹/논 블로킹: (다음 사람이) 응답을 기다리냐 아니냐의 차이
Sync/Async: 응답 받고 내가 하냐 다른 사람이 하냐의 차이.
1. 블로킹 vs. 논 블로킹: 요청받은 함수가 cpu 제어권(함수 실행권)을 언제 넘겨주냐의 차이로, 작업(제어권) 관점에 해당한다.
- 블로킹: 요청받은 함수가 작업을 다 마치고 나서 요청자에게 제어권을 넘겨준다. 그동안 요청자는 아무 것도 하지 않고 기다린다.
- 논 블로킹: 요청받은 함수가 요청자에게 제어권을 곧바로 넘겨준다. 이러면 요청자는 곧바로 자기 일을 수행할 수 있다.
2. 동기 vs. 비동기: 요청받은 함수가 작업을 완료했는지를 누가 체크하냐의 차이로, 시간 관점에 해당한다. 요청자가 요청받는 애의 작업이 끝났는지를 계속해서 체크하는지 아니면 요청받은 애가 알아서 끝났다고 보고하는지의 차이다.
- 동기: 요청자가 요청받은 함수의 작업이 완료됐는지를 계속 확인한다(여러 함수들이 시간에 맞춰 실행/종료된다.)
- 비동기: 요청자는 요청 후 신경쓰지 않으며, 요청받은 함수가 작업을 마치면 보고한다(함수의 작업 시작/종료 시간이 맞지 않는다.)