라봉이의 개발 블로그

[node js] node.js 동작 원리, node.js 구조 및 시스템, event loop 구조 본문

Node.js

[node js] node.js 동작 원리, node.js 구조 및 시스템, event loop 구조

Labhong 2018. 3. 30. 21:56
반응형

그동안 노드js를 사용하면서 항상 머리 속을 맴돌았던 질문이 있었다.


노드는 어떻게 돌아가는 거지? 노드는 왜 싱글 스레드인거지? 뭐가 대체 싱글 스레드인걸까??


궁금증에 못이겨 노드js 공식 홈페이지의 문서를 뒤젹거리며 찾아본 결과를 정리해보고자 포스팅을 시작했다. 문서 내용을 가져온 내용이 있기 때문에 직접 Node.js 공식 문서를 보는 것도 좋은 방법일 것 같다.


Node.js 공식 문서: https://nodejs.org/ko/docs/


블로킹과 논블로킹 살펴보기


먼저 블로킹과 논블로킹에 대해 알아보도록 하겠습니다.

블로킹: 호출되는 함수가 자신의 작업을 마칠 때까지 제어권을 넘겨주지 않고 대기하는 방식

논블로킹: 호출되는 함수가 바로 제어권을 넘겨줘서 다른 작업을 진행할 수 있도록 하는 방식


Node.js에서 libuv를 사용하는 Node.js 표준 라이브러리의 동기 메서드가 가장 대표적인 블로킹 작업입니다. 네이티브 모듈도 블로킹 메서드를 가질 수 있습니다. (이런 동기 메서드는 이름 마지막에 Sync가 붙습니다.)


그 외의 Node.js 표준 라이브러리의 모든 I/O 메서드논블로킹인 비동기 방식을 제공하고 콜백 함수를 받습니다.



1) 코드 비교

블로킹 메서드와 논블로킹 메서드 예제
Ex) 동기로 파일을 읽는 예제

const fs = require('fs');
const data = fs.readFileSync('/file.md'); // 파일을 읽을 때까지 여기서 블로킹됩니다.
console.log(data);
// moreWork();는 console.log 이후 실행될 것입니다.
// 싱글 스레드가 파일 읽기 작업 완료 후 moreWork() 수행 


Ex) 비동기로 파일을 읽는 예제

const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
  if (err) throw err;
  console.log(data);
});
// moreWork();는 console.log 이전에 실행될 것입니다.
// 파일을 읽는 작업은 다른 스레드에 맡긴 뒤 끝나면 Node.js에게 알려주어 적절한 콜백을 처리


파일 읽기가 완료되길 기다리지 않고 moreWork()를 실행할 수 있도록 한 것은 높은 처리율을 가능하게 하는 Node.js 핵심 디자인 철학입니다. (비동기)


동시성(Concurrency)과 처리율(Throughput)


Node.js에서 JavaScript 실행이 싱글 스레드(이벤트 루프와 같은 스레드)에서 동작합니다. 따라서 Node.js의 동시성은 다른 작업이 완료된 후에 JavaScript 콜백 함수를 실행하는 이벤트 루프의 능력을 의미합니다. 다시 말해서 javascript 실행은 하나의 이벤트 루프에서만 동작하지만 이벤트 루프의 여러 콜백 함수를 실행하여 동시에 처리되도록 보이는 것을 의미합니다.

동시에 실행되어야 하는 모든 코드는 I/O 등의 JavaScript가 아닌 작업(이벤트 루프 스레드 외 다른 스레드)이 일어나는 동안 이벤트 루프가 계속 실행될 수 있도록 해야 합니다.


이벤트 루프는 동시 작업을 다루려고 부가적인 스레드를 만드는 다른 언어의 모델과는 다릅니다. (Node.js는 스레드를 따로 생성하지 않음) => (node.js 10버전으로 들어오면서 Worker_thread라는 모듈을 통해 스레드 생성 가능하도록 변경됐다.)


node.js 시스템 구조



libuv비동기 I/O에 집중하는 멀티 플랫폼 라이브러리이다. C언어로 개발되었으며 사실상 Node.js를 위해 개발된 것이다. 이 라이브러리는 다양한 I/O 폴링 메커니즘에 대한 단순한 추상화 이상의 기능을 제공한다. 어느 운영체제에서도 가능한 file I/Othread 기능 또한 제공된다. 

libuv에는 thread pool이 존재하는데 이 thread pool에 있는 thread가 동기적인 입출력 작업을 이벤트 루프 대신 처리를 해준다. 또한 이 libuv를 통해 이벤트 루프를 제어한다.

libuv 홈페이지: http://libuv.org/


Node.js는 binding API(C++ Addon)를 활용해 위에서 설명한 libuv과 연결되며 I/O 작업을 동기식으로 libuv가 처리한 뒤 I/O 작업이 끝나면 콜백함수가 처리되는 것이다.

I/O 작업네트워크, 파일 시스템, 프로세스 등이 있다.

따라서 I/O 작업이 많은 곳에서 Node.js가 활용이 좋다. (여러 스레드를 활용하기 때문)

Node.js 코드를 수행하는 스레드와 이벤트 루프의 스레드는 동일하다. 



이벤트 루프


이벤트 루프는 가능하다면 언제나 시스템 커널에 작업을 떠넘겨서 Node.js가 논블로킹 I/O 작업을 수행하도록 해줍니다.(노드를 효율적으로 운용하는 방법)


대부분의 현대 커널은 멀티 스레드이므로 백그라운드에서 다수의 작업을 실행할 수 있습니다. 이러한 작업 중 하나가 완료되면 커널이 Node.js에게 알려주어 콜백 함수를 poll 큐에 추가할 수 있게 하여 동시성 있게 처리하도록 합니다. 



위의 그림은 이벤트 루프의 작업 순서의 간단한 개요를 보여준다.


  • Timers: 이 단계는 setTimeout()과 setInterval()로 스케줄링한 콜백을 실행합니다.
  • I/O callbacks: 클로즈 콜백, 타이버로 스케줄링된 콜백, setImmediate()를 제외한 거의 모든 콜백을 실행합니다.
  • Idle, prepare: 내부용으로만 사용합니다.
  • Poll: 새로운 I/O 이벤트를 가져옵니다. 적절한 시기에 node는 여기서 블록합니다.
  • Check: setImmediate() 콜백은 여기서 호출됩니다.
  • Close callbacks: 예시: socket.on(‘close’, ….);


이벤트 루프는 이 모든 큐 들에 존재하는 작업들을 순차적으로 처리함으로써 비즈니스 로직을 수행한다.


반응형
Comments