NodeJS의 동작 원리를 알기 위해서는 javascript 동작 원리를 먼저 알아야한다.
그래서 먼저 javascript는 어떻게 동작하는지 알아보려고 한다~~
javascript는 싱글 스레드로 동작하는 언어이다.
싱글스레드이기 때문에 한 번에 하나의 작업만 처리할 수 있으며, 하나의 메인 스레드와 하나의 콜 스택을 가지고 있다.
자바스크립트 엔진의 대표적인 예는 V8엔진으로, V8은 크롬과 Nodejs에서 사용한다.
메모리 할당이 일어나는 곳
(Heap
: 구조화되지 않은 넓은 메모리 영역으로 객체(선언해주는 변수, 함수 등)이 담김)
call stack은 우리가 어디에 있는지 기록하는 자료구조이다.
하나의 메인 스레드에서 호출되는 함수들은 콜 스택에 쌓이고, 쌓인 함수들은 LIFO(Last In First Out) 방식으로 실행된다.
function multiply(x, y) {
return x * y;
}
function printSquare(x) {
const s = multiply(x, x);
console.log(s);
}
printSquare(5);
Uncaught RangeError: Maximum call stack size exceeded
함수 호출 횟수가 call stack의 최대 허용치를 넘게 되면 발생하는 에러
브라우저 or 엔진마다 call stack의 한계가 다르다.
비동기 처리 담당한다.
비동기 처리가 끝난 후 실행되어야 할 콜백 함수가 차례로 할당된다.
callback queue에 할당된 함수를 순서에 맞춰 call Stack에 할당해준다.
단, call stack이 비어있는 경우에만 테스트 큐에 콜백 함수를 넘겨준다.
만약 http 요청을 동기로 수행한다면 해당 함수가 콜 스택에 쌓인 채로 머물고, js 엔진은 해당 작업이 끝날 때까지 어떤 작업도 수행할 수 없다. 즉 동기 작업이 다른 코드들을 블로킹하게 되는 것이다.
하지만 javascript는 비동기 작업들을 Web API에게 넘겨줄 수 있기 때문에 해당 작업이 완료될 때까지 다른 코드들을 실행할 수가 있다. 이게 바로 논블로킹
이다.
비동기적인 코드 내부 동작을 살펴보자.
예시1
console.log("시작");
setTimeout(function() {
console.log("중간");}, 3000);
console.log("끝");
//시작 -> 끝 -> 중간으로 출력
--짤 추가 예정!!!--
*anonymous
: 모든 코드를 가지고 있는 것
JS에서 Web API가 지원하는 setTimeout 같은 코드가 실행되는 순서를 알아보자.
1. setTimeout 코드가 call stack에 쌓인다.
2. javascript 엔진은 비동기 작업은 Web API에게 위임한다.
3. Web API는 해당 비동기 작업을 수행하고, callback 함수를 callback queue에세 넘겨준다
4. event loop는 call stack에 쌓여있는 함수가 없을 때, callback queue에서 대기하고 있던 callback 함수를 call stack으로 넘겨준다.
5. call stack에 쌓인 callback 함수가 실행되고 call stack에서 제거된다.
예시2
console.log("시작");
setTimeout(function() {
console.log("중간");}, 0
);
console.log("끝");
//시작 -> 끝 -> 중간으로 출력
시간이 중요한게 아니라 setTimeout 함수가 Web API가 지원하는 비동기 함수라는 것이 중요하다. setTimeout은 바로 콜 스택에 쌓여 처리되는 것이 아니라 Web API에서 비동기로 처리된 후 callback 함수가 callback queue에 전달된다. 따라서 시간이 0초여도 콜스택에 바로 쌓이는 다른 함수보다 늦게 호출된다.
예시3
console.log("시작");
setTimeout(function() {
console.log("중간");}, 0
);
Prmise.reslove()
.then(function() {
console.log("프로미스");
}
);
console.log("끝");
//시작 -> 끝 -> 프로미스 -> 중간으로 출력
주의해야할 점 promise 자체는 동기지만, then을 만나는 순간 비동기로 동작하게 된다.
javascript를 브라우저 밖에서도 실행시킬 수 있는 javscript 런타임이다
즉, javascript를 실행시킬 수 있는 환경이란 뜻이다.
Nodejs는 싱글 스레드이다. 싱글 스레드는 프로세스 내에서 하나의 스레드가 하나의 요청만 수행하는 것으로 한 번에 여러 요청을 수행할 수 없다. 그래서 싱글 스레드는 블로킹 모델이라고 한다.
하지만 Nodejs는 싱글 스레드 논블로킹
모델이다. 싱글 스레드이지만 비동기 I/O 작업을 통해 요청들을 서로 블로킹하지 않는다. 즉 동시에 많은 요청들을 비동기로 수행함으로써 싱글스레드이지만 논블로킹이 가능하다.
그럼 Nodejs는 완전한 싱글 스레드인가??
Nodejs는 싱글 스레드이긴 하지만 완전한 싱글 스레드 기반으로 동작하지는 않는다고 한다. 일부 블로킹 작업들은 libuv
의 스레드 풀에서 수행되기 때문이라고 한다.
무슨 소린지 모르겠으니 Nodejs의 내부 구조부터 살펴보자!!
Nodejs를 크게 나눠보면 내장 라이브러리와 V8 엔진, libuv로 구성된다.
Nodejs의 특성인 이벤트 기반, 논블로킹 I/O 모델들은 모두 libuv 라이브러리에서 구현된다고 한다.
NodeJS에서 작성되는 거의 모든 코드들은 콜백 함수로 이루어져 있다. 콜백 함수들은 libuv 내에 위치한 이벤트 루프에서 관리 및 처리가 된다.
이벤트 루프는 여러 개의 페이즈들을 갖고 있으며 해당 페이즈들은 각자만의 큐를 가지고 있다. 이벤트 루프는 라운드 로빈 방식으로 노드 프로세스가 종료될 때까지 여러 페이즈들을 계속 순회하고, 페이즈들은 각각의 큐를 관리하고 각각의 큐는 FIFO 순서로 콜백 함수들을 처리하게 된다.
(뭔소릴까...)
NodeJs에서 논블로킹 I/O 모델은 input output이 관련된 작업(데이터베이스 CRUD, 파일시스템 등)의 블로킹 작업들은 백그라운드에서 수행하고, 이를 비동기 콜백 함수로 이벤트 루프에 전달하는 것을 의미한다.