[Node.js]Node.js의 특성

Yeon Jeffrey Seo·2021년 12월 29일
0

Node.js

목록 보기
4/4

1. Node.js는 자바스크립트 런타임이다.

Node.js는 Chrome V8 JavasScript 엔진으로 빌드된 JavaScript 런타임이다.

노드는 자바스크립트 런타임이다. 런타임이란, 특정 언어로 만든 프로그램을 실행할 수 있는 환경을 뜻한다.
기존에는 자바스크립트 프로그램을 웹 브라우저 위에서만 실행할 수 있었다. 하지만 Node.js의 등장으로, 자바스크립트 프로그램을 컴퓨터에서 실행할 수 있게 되었다.

노드의 내부 구조

노드는 V8과, libuv라는 라이브러리를 사용한다. libuv 라이브러리는 노드의 특성인 이벤트 기반, 논 블로킹 I/O 모델을 구현하고 있다.

2. 이벤트 기반(Event-driven)

이벤트 기반이란?
이벤트가 발생할 때 미리 지정해둔 작업을 수행하는 방식. 클릭, 네트워크 요청 등.

이벤트 기반 시스템에서는 특정 이벤트가 발생할 때 무엇을 할 지 미리 등록한다 -> 이벤트 리스너에 콜백 함수를 등록한다고 표현한다.

이벤트 루프(Event loop)

노드는 자바스크립트 코드의 맨 위부터 한 줄씩 실행한다. 함수 호출 부분을 발견하면, 호출한 함수를 호출 스택(call stack) 에 넣는다.

function first() {
	second();
  	console.log("첫 번째");
}

function second() {
	third();
  	console.log("두 번째");
}

function third() {
	console.log("세 번째");
}

first();

first()를 호출하면, first() 내의 second()를 호출하고, second()는 third()를 호출한다.

그림 추가

호출 스택의 최하단에 위치하는 anonymous 함수는 처음 실행 시의 전역 컨텍스트(global context)를 의미한다.
컨텍스트는 함수가 호출되었을 때 생성되는 환경을 의미한다.
자바스크립트 코드는 실행 시 기본적으로 전역 컨텍스트 안에서 돌아간다.
함수는 실행되는 동안 호출 스택에 머물러 있다가 실행이 완료되면 호출 스택에서 지워진다.

  • 이벤트 루프
    이벤트 발생 시 호출할 콜백 함수들을 관리하고, 호출된 콜백 함수의 실행 순서를 결정하는 역할을 담당한다. 노드가 종료될 때까지 이벤트 처리를 위한 작업을 반복하므로 루프(loop)라고 부른다.
  • 백그라운드
    setTimeOut 같은 타이머나, 이벤트 리스너들이 대기하는 곳. 여러 작업이 동시에 실행될 수 있다.
  • 테스크 큐
    이벤트 발생 후, 백그라운드에서는 타이머나 이벤트 리스너의 콜백 함수를 테스크 큐로 보낸다. 정해진 순서대로 콜백들이 줄을 서 있으므로 콜백 큐라고 부른다. 콜백들은 보통 완료된 순서대로 줄을 서 있지만, 특정한 경우에는 순서가 바뀌기도 한다.

이벤트 루프는 호출 스택이 비어 있으면(anonymous 까지 실행이 완료되면) 태스크 큐에서 함수를 하나씩 가져와 호출 스택에 넣고 실행한다. 만약 호출 스택에 함수들이 너무 많이 들어 있으면, setTimeOut의 시간이 정확하지 않을 수 있다. 왜냐하면 이벤트 루프는 호출 스택이 비어 있을 때만 태스크 큐에 있는 run 함수를 호출 스택으로 가져오기 때문이다.

3. 논 블로킹 I/O

작업에는 두 가지 종류가 있는데, 동시에 실행될 수 있는 작업동시에 실행될 수 없는 작업 이 있다.
기본적으로 우리가 작성하는 자바스크립트 코드는 동시에 실행될 수 없다. 하지만 자바스크립트 상에서 돌아가는 것이 아닌 I/O 작업 같은 것들은 동시에 처리될 수 있다.

  • I/O
    입력/출력을 의미하며, 파일 시스템 접근, 네트워크를 통한 요청과 같은 작업이 I/O의 일종이다. Node는 이러한 작업을 논블로킹 방식으로 처리하는 방법을 제공한다.
  • 논 블로킹
    이전 작업이 완료될 때까지 대기하지 않고 다음 작업을 수행하는 것.
  • 블로킹
    이전 작업이 끝나야만 다음 작업을 수행하는 것

노드는 I/O 작업을 백그라운드로 넘겨 동시에 처리하곤 한다. 따라서, 위에서 언급했던, 동시에 처리될 수 있는 작업들 은 최대한 묶어서 백그라운드로 넘겨야 시간을 절약할 수 있다.
따라서 코딩을 할 때 논 블로킹 방식으로 코딩하는 습관을 들여야 한다.
아래 코드는 블로킹 방식의 코드.

function longRunningTask(){
	// 오래 걸리는 작업
  	console.log("작업 끝");
}

console.log("시작");
longRunningTask();
console.log("다음 작업");

실행 결과
시작 -> 작업 끝 -> 다음 작업

작업을 수행하는데 오래 걸리는 longRunningTask() 함수가 있고, 이 함수가 블로킹 방식의 I/O 작업을 한다면, 이 작업이 완료되기 전까지는 이어지는 console.log("다음 작업")이 호출되지 않는다.

이를 개선해보면,

function longRunningTask() {
	// 오래 걸리는 작업
  	console.log("작업 끝");
}
console.log("시작");
setTimeOut(longRunningTask, 0);
console.log("다음 작업);

실행 결과
시작 -> 다음작업 -> 작업 끝

setTimeOut()은 코드를 논 블로킹으로 반들기 위해 사용하는 기법 중 하나이다. setTimeOut()내의 콜백이 태스크 큐로 보내지므로, 호출 스택에 있는 함수들(console.log("시작"), console.log("다음 작업"))이 실행된 뒤에 longRunningTask가 실행된다.
다만!!! 논 블로킹 방식으로 코드를 작성하더라도 코드가 전부 직접 작성한 것이라면 전체 소요 시간이 짧아지지 않는다. 왜냐하면 여기에서 작성한 함수들은 동시에 처리될 수 없는 작업들이기 때문이다. 단지 실행 순서만 바뀐다.
그렇다면 위 코드는 무슨 의미를 가지고 있지....? 위 코드는 오래 걸리는 작업 때문에 간단한 작업들이 대기하는 사황을 막을 수 있다. 동시성 != 논 블로킹

4. 싱글 스레드

Node는 싱글 스레드이다.

  • 프로세스
    운영체제에서 할당하는 작업의 단위. 노드, 웹 브라우저 같은 프로그램은 개별적인 프로세스이다. 프로세스 간에는 메모리 등의 자원을 공유하지 않는다.
  • 스레드
    프로세스 내에서 실행되는 흐름의 단위. 프로세스는 스레드를 여러 개 생성해 여러 작업을 동시에 처리할 수 있다. 스레드들은 부모 프로세스의 자원을 공유한다. 같은 주소의 메모리에 접근 가능하므로 데이터 공유 가능.

Node를 실행하면, 내부적으로 여러 개의 스레드가 생성된다. 하지만 사용자가 직접 제어할 수 있는 스레드는 단 하나 뿐이기 때문에 Node는 싱글 스레드라고 여겨진다.

  • 스레드 풀과 워커 스레드
    노드가 싱글 스레드로 동작하지 않는 두 가지 경우가 있다. 하나는 스레드 풀(Thread Pool)이고, 다른 하나는 워커 스레드(Worker Thread)이다.
    스레드 풀은 노드가 특정 동작을 수행할 때 스스로 멀티 스레드를 사용한다. Ex. 암호화, 파일 입출력, 압축
    워커 스레드는 노드 v12에서 안정화된 기능이다. 사용자가 직접 다수의 스레드를 사용할 수 있게 만들어준다.

스레드와 블로킹의 비유
식당에 직원이 있고, 이 직원은 주문을 받고 음식을 서빙한다. 손님은 여러 명이 올 수 있다.
1. 싱글 스레드 & 블로킹 모델
직원이 1명 있고, 이 직원은 현재 주문 받은 손님의 음식이 나와 서빙을 완료하기 전엔 그 다음 주문을 받지 않는다. 반드시 손님에게 서빙을 한 뒤 다음 손님의 주문을 받는다. 매우 비효율적!!
2. 싱글 스레드 & 논 블로킹 모델 : 노드의 방식
직원이 1명 있다. 이 직원은 한 손님의 주문을 받아 주문 내역을 주방에 넘기고, 다음 손님의 주문을 받는다. 요리가 끝날 때까지 기다리는게 아니라, 주문이 들어온다는 것을 계속 주방에 알려주고 있다. 서빙이 되는 순서는 요리의 특성(블로킹 혹은 논 블로킹)에 따라 다를 수 있다.

싱글 스레드, Non-blocking I/O, 이벤트 루프에 대한 나의 생각

Node의 스레드가 하나라는 것은, 다시 말해 사용자가 직접 관리할 수 있는 호출 스택은 하나인 것을 의미한다. 이 호출 스택에서, Non-blocking, 이벤트 주도 방식이 이루어진다. 호출 스택에서 I/O 작업을 만날 경우(비동기 처리가 필요한 경우) 이를 백그라운드로 던져 놓고 호출 스택의 다음 함수를 호출하는 것이 Non-blocking이다. I/O 작업의 결과를 기다리지 않고 다음 함수를 호출하기 때문.

동기/비동기 작업의 경우, 이벤트 루프에서 관장한다.

I/O 작업의 경우, 시간이 비교적 오래 걸리므로 해당 작업의 결과를 받은 뒤에 그 다음 작업을 수행해야 하는 경우가 많다. (promise의 fulfilled or rejected) 이러한 단일 작업의 로직 내에서 결과를 받은 뒤 다음 로직을 수행할 땐 동기적 프로그래밍을 한다.
아 참, I/O 작업의 예시는 다양하다. DB 트랜잭션, 네트워크 작업(외부 API 호출 등), 파일 시스템 작업 등이 있다.

profile
The best time to plant a tree was twenty years ago. The second best time is now.

0개의 댓글