노드 런타임 환경에서의 동작 방식은 브라우저에서의 자바스크립트 엔진이 동작하는 방식과 같다.
NodeJS 런타임 환경에 작성한 Source code를 동작하게 하면, 실제로는 Node.JS 어플리케이션 형태로 (어플리케이션 레벨에서) 동작하게 된다. 어플리케이션 안에는 동적으로 생성한 데이터를 보관하는 Heap 영역, 함수의 실행 순서를 기억하는 CallStack 영역이 있다.
setTimeout에서 지정한 시간이 흐른 후, 원하는 콜백함수를 호출해달라고 던져준 다음에 기다리지 않고 바로 second() 로 넘어가게된다.(Non-blocking 방식)
그러면 노드js 내부에서 타이머를 시작하고, 콜스택은 다시 순차적으로 함수를 실행하게 된다. 그러다 지정한 시간이 흐르면, node js는 호출해야하는 콜백을 Task Queue에 넣어준다.
이벤트 루프는 콜스택이 비워질때까지 기다렸다가, Task Queue에 있는 콜백함수를 Call Stack으로 가져간다.
정리를 해보자면 자바스크립트로 만들어진 어플리케이션은 Single Thread 지만, nodejs 런타임 환경은 멀티쓰레딩이 가능하기 때문에 콜백함수를 던져주면 알아서 병렬적으로 처리가 되다가, 완료되는 이벤트가 발생하면 등록한 콜백함수를 Task Queue에 옮겨준다.
정리 📚
node.js 어플리케이션은 ‘main single thread’ 를 가진다. 즉, 어플리케이션에서 필요한 일들을 처리하는 메인 스레드가 있고, 파일을 읽고 쓰고, 네트워크 요청을 하는 등의 일들은 node.js가 제공해주는 APIs 들을 이용해서 할 수 있다. 이 때, 이벤트가 발생했을때 처리해야할 것들을 콜백 형태로 전달해주면 node.js 내부적으로 병렬적으로 처리해준다.
이것이 가능한 이유는 node.js 내부에는 Javascript engine(V8) 도 있고, Libuv 라는 c언어로 작성된 라이브러리(운영체제별로 파일을 읽고 쓰거나 네트워크에 읽고 쓰는 등의 태스크를 비동기적으로 할 수 있게 도와주는 라이브러리)를 통해 병렬적으로 처리가 가능하다. (Libuv가 각 운영체제별로 알아서 처리해주기때문에 운영체제마다 달라지는 것에 신경쓰지 않아도 된다.)
이들 외에도 http를 파싱하는 llhttp 라이브러리 모듈, crpyto/tls를 담당하는 ssl 라이브러리 모듈, dns를 요청하는 c-ares 모듈, 데이터를 압축하고 해제하는 zlib 모듈 등을 node.js 자체에서 가지고 있다.
중요한 포인트는 어플리케이션은 ‘Main Single Thread’ 에서 동작한다는 것이다.
그 말은 즉, 등록한 콜백함수는 결국 Single Thread에서 동작하게 되기 때문에, 뭔가 무거운 일을 콜백 함수로 등록하게 되면 그 일이 끝나기전까지 다음 콜스택으로 넘어갈 수 없다.
그렇기 때문에 어플리케이션과 콜백 함수에 작성된 코드는 가벼운 일들만 처리해야한다.
Don’t block the event loop, keep it running and avoid anything that could block the thread-like synchronous network calls or infinite loops
즉, 오래 걸리는 일들을 콜백 함수로 등록하여서 이벤트 루프를 막아선 안된다 🙂
node.js 싱글 스레드
node.js 는 Non-blocking I/O , Event-Driven 방식으로 되어있기때문에 멀티스레딩 방식보다 더 효율적으로 I/O 처리가 가능하다.
단, CPU 면에서 봤을때는 node.js는 적합하지 않다. node.js 자체적으로는 싱글스레드로 동작하기 때문에 무거운 계산을 하는 일들엔 적합하지 않다.
node.js 12+ 버전부터는 무거운 계산 (이미지를 리사이징한다던지, 비디오를 인코딩한다던지..) 이 필요한 작업에는 ‘worker threads’ 라는 api를 활용할 수 있다!
문득 궁금한점은,
만약 setTimeout 시간을 1초로 설정하고 1초가 지난 뒤에 콜백함수를 실행할 시점에 Call Stack이 비어있지 않다면, 1초라는 지정한 시간을 보장할 수 있을까 ? 🤔
찾아보니… 정답은 “시간을 완벽하게 보장할 수 없다” 라는 것이었다. 워낙 처리속도가 빠르기때문에 1초만에 나오는거라고 생각되는 것이지 완벽한 1초는 아니라는 것이다..