[Node.js] study 3주차 - 비동기 (non-blocking I/O, single thread, 이벤트 루프)

newsilver·2022년 2월 11일
2

react-study

목록 보기
4/9
post-thumbnail

Node.js non-blocking I/O 란?

non-blocking I/O

예를 들어 서버에 다음과 같은 요청을 보낸다고 가정했을 때

A : 10초
B : 10초
C : 500초
D : 10초

non-blocking 특성을 가지고 있지 않는 서버는

A -> B -> C(500초 대기) -> D 

이렇게 위에서부터 차례로 하나씩 처리한다.
D는 C의 요청이 끝날 때까지 오랜 시간 대기해야 하는데, 위와 같은 상황이 blocking이다.
Javascript에서 다음 Javascript 코드가 수행되기 위해 현재의 Javascript or non-javascript 연산이 끝날 때 까지 기다려야 하는 상황이다.
이벤트 루프가 blocking 작업을 하는 동안 javascript 실행을 계속할 수 없기 때문이다.

반대로 non-blocking먼저 요청을 모두 접수하고 빨리 완료된 순서대로 처리해준다.

A -> B -> D -> C 

정리하자면 블로킹 메서드는 동기로 실행되고 논블로킹 메서드는 비동기로 실행된다.

blocking이 필요한 상황을 살펴보자.

const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
  if (err) throw err;
  console.log(data);
});
fs.unlinkSync('/file.md');

위 코드는 readFile() 이 실행되기 전에 unlinkSync() 가 먼저 실행되므로 제대로 작동하기 위해서는 완전히 논블로킹으로 작성해 작업 순서가 올바르도록 보장해야한다.

const fs = require('fs');
fs.readFile('/file.md', (readFileErr, data) => {
  if (readFileErr) throw readFileErr;
  console.log(data);
  fs.unlink('/file.md', (unlinkErr) => {
    if (unlinkErr) throw unlinkErr;
  });
});

Node.js의 모든 I/O 메서드는 논블로킹인 비동기 방식을 제공하고 콜백 함수를 받는다. 일부 메서드는 같은 작업을 하는 블로킹 메서드도 가지는데 이름 마지막에 Sync가 붙는다.

Node.js의 non-blocking I/O

Node.js에서의 논블로킹 I/O 모델은 블로킹 작업(Input, Output과 관련된 작업 / http, Database CRUD, third party api, filesystem)들을 백그라운드(libuv의 스레드 풀)에서 수행하고, 이를 비동기 콜백함수로 이벤트 루프에 전달하는 것을 말한다.

Node.js는 싱글스레드인가요?

Node.js는 싱글스레드이다. (완전히는 아니지만)
먼저 싱글스레드와 멀티 스레드를 간단히 알아보자.

  • 싱글 스레드 : 프로세스 내에서 하나의 스레드가 하나의 요청만을 수행한다. 해당 요청이 수행될 때 다른 요청을 함께 수행할 수 없다.
  • 멀티스레드 : 스레드 풀에서 실행의 요청만큼 스레드를 매칭하여 작업을 수행한다.

멀티스레드는 스레드 풀에 스레드가 늘어날수록 CPU 비용을 소모하고, 만약 요청이 적다면 놀고있는 스레드가 생긴다는 단점이 있다.
다시 말해 효율성이 낮다.

Node.js는 싱글스레드 논블로킹 모델로 구성되어 있다. 하나의 스레드로 동작하지만, 비동기 I/O 작업을 통해 요청들을 서로 블로킹하지 않는다. 즉, 동시에 많은 요청들을 비동기로 수행함으로써 싱글스레드일지라도 논블로킹이 가능하다.

Node.js는 완전한 싱글스레드인가요?

Node.js가 완전히 싱글스레드가 아닌 이유는 일부 Blocking 작업들은 libuv의 스레드 풀(Thread pool)에서 수행되는데, 이 스레드 풀이 멀티스레드이기 때문이다.

V8 엔진에서 Javascript를 C++로 Translate 해주기 때문에 C/C++언어를 몰라도 Node.js를 사용할 수 있다.

이벤트 루프

Node.js는 이벤트 리스너에 등록해둔 콜백함수를 실행하는 방식으로 동작한다. 이를 이벤트 기반(Event-driven)이라고 하고, 이벤트가 발생할 때 미리 지정해둔 작업을 수행하는 방식을 의미한다.
이벤트에 따라 호출되는 콜백함수를 관리하는 것이 이벤트 루프이다.

이벤트 루프는 6개의 페이즈를 가지고 있으며, 페이즈를 라운드 로빈 방식으로 순회한다. 페이즈는 각자마다 큐를 가지고 있고, FIFO(First In First Out) 순서로 콜백 함수들을 처리한다.

라운드 로빈 : 프로세스들에게 공정하게 부여하기 위한 스케줄링의 한 방법이다. 
각 프로세스에 일정시간을 할당하고, 할당된 시간이 지나면 그 프로세스는 맨 뒤로 보내고, 
다른 프로세스에게 기회를 주고, 또 그 다음 프로세스에게 기회를 할당하는 방식으로
돌아가며 기회를 부여하는 운영방식이다.

1. timers

setTimeout()setInterval() 같은 timer 함수들이 처리된다.

2. pending callbacks

연기된 I/O 완료 결과가 큐에 담긴다.
I/O 작업 블록 내의 콜백함수들을 poll단계의 큐로 넘겨준다.

3. idle, prepare

내부적으로만 사용된다. (자바스크립트가 실행되지 않음.)

4. poll

I/O와 연관된 콜백(클로즈 콜백, 타이머로 스케줄링된 콜백, setImmediate()를 제외한 거의 모든 콜백)을 실행한다.
(+ timer 단계에서의 실행 시간 제어를 담당한다.)
poll 큐에 쌓인 콜백함수들을 한도가 넘지 않을때까지 모두 동기적으로 실행한다.
한도가 넘거나, 더이상 실행할 콜백함수가 없을때는 다음 규칙을 따라, 다음 단계로 넘어가거나 대기한다.

1. check 단계를 검사하여 setImmediate() 가 있는지 확인
  - check 단계에 setImmediate()가 있는 경우에는 check 단계로 넘어감.
  - 없다면, timer 단계에서 실행할 timer 함수가 있는지 확인
2. timer 함수를 실행할 수 있는 시간까지 대기한 후에, timer 단계로 넘어감.
  - 대기하는 동안에 poll 큐에 콜백함수가 쌓인다면 즉시 실행

5. check

setImmediate() 의 콜백함수가 실행된다.
poll 단계에서 작업을 수행 후 poll 단계가 유휴 상태가 되었다면 poll 이벤트를 기다리지 않고 check 단계로 넘어간다.

유휴 상태 : 어떠한 프로그램에 의해서도 사용되지 않는 상태. 모든 태스트를 끝내면 유휴 상태가 된다.

6.close callbacks

close 이벤트에 따른 콜백함수를 실행한다.

profile
이게 왜 🐷

0개의 댓글