노드는 V8과 더불어 libuv라는 라이브러리를 사용한다.
V8과 libuv는 C와 C++로 구현되어 있으며, 코딩한 자바스크립트 코드는 노드가 알아서 V8과 libuv에 연결해주므로 C와 C++는 몰라도 된다.
libuv 라이브러리는 노드의 특성인 이벤트 기반, 논블로킹 I/O 모델을 구현하고 있다.
이벤트 기반
이벤트가 발생할 때 미리 지정해둔 작업을 수행하는 방식을 의미한다.
이벤트로는 클릭이나 네트워크 요청 등이 있을 수 있다.
이벤트 기반 시스템에서는 특정 이벤트가 발생할 때 무엇을 할 지 미리 등록해둬야 한다.
이를 이벤트 리스너에 콜백 함수를 등록한다고 표현한다.
ex) 버튼을 클릭할 때 경고창을 띄우도록 설정하는 것을 예로 들어보자.
클릭 이벤트 리스너에 경고창을 띄우는 콜백 함수를 등록해두면 클릭 이벤트가 발생할 때마다 콜백 함수가 실행돼 경고창이 뜨는 것이다.
노드도 이벤트 기반 방식으로 동작하므로, 이벤트가 발생하면 이벤트 리스너에 등록해둔 콜백 함수를 호출한다.
이벤트 기반 모델에서는 이벤트 루프라는 개념이 등장한다. 여러 이벤트가 동시에 발생했을 때 어떤 순서로 콜백 함수를 호출할지를 이벤트 루프가 판단한다.
노드와 자바스크립트에서 이벤트 루프는 정말 중요한 개념이니 공부하길 바란다.
노드는 자바스크립트 코드의 맨 위부터 한 줄씩 실행한다.
함수 호출 부분을 발견했다면 호출한 함수를 호출 스택에 넣는다.
function first() {
second();
console.log('첫 번째');
}
function second() {
third();
console.log('두 번째');
}
function third() {
console.log('세 번째');
}
first();
first 함수가 제일 먼저 호출되고, 그 안의 second 함수가 호출된 뒤, 마지막으로 third 함수가 호출된다.
anonymous 함수는 처음 실행 시의 전역 콘텍스트를 의미한다.
콘텍스트는 함수가 호출되었을 때 생성되는 환경을 의미한다.
함수는 실행되는 동안 호출 스택에 머물러 있다가 실행이 완료되면 호출 스택에서 지워진다.
이번에는 특정 밀리초 이후에 코드를 실행하는 setTimeout를 사용해보겠다.
function run() {
console.log('3초 후 실행');
}
console.log('시작');
setTimeout(run, 3000);
console.log('끝');
3초 뒤에 run 함수를 실행하는 코드이다.
setTimeout 함수의 콜백인 run이 호출 스택에 언제 들어가는지 지금까지 배운 내용으로는 알기 어렵다.
이를 파악하기 위해서는 이벤트 루프, 태스크 큐, 백그라운드를 알아야 한다.
- 이벤트 루프
- 이벤트 발생 시 호출할 콜백 함수들을 관리하고, 호출된 콜백 함수의 실행 순서를 결정하는 역할
- 백그라운드
- setTimeout 같은 타이머나 이벤트 리스너들이 대기하는 곳이다.
- 태스크 큐
- 이벤트 발생 후, 백그라운드에서는 태스크 큐로 타이머나 이벤트 리스너의 콜백 함수를 보낸다.
- 정해진 순서대로 콜백들이 줄을 서 있으므로 콜백 큐라고도 한다.
논블로킹 I / O
이벤트 루프를 잘 활용하면 오래 걸리는 작업을 효율적으로 처리할 수 있다.
동시에 실행될 수 있는 작업과 동시에 실행될 수 없는 작업이다.
I / O는 입력 과 출력을 의미하며, 파일 시스템 접근이나 네트워크를 통한 요청 같은 작업이 I / O 의 일종이다.
논블로킹이란 이전 작업이 완료될 때까지 대기하지 않고 다음 작업을 수행하는 것을 의미한다.
블로킹이란 이전 작업이 끝나야만 다음 작업을 수행하는 것을 의미한다.
블로킹 방식의 코드
function longRunningTask() {
console.log('작업 끝');
}
console.log('시작');
longRunningTask();
console.log('다음 작업');
이번에는 setTimeout을 사용해서 코드를 바꿔보겠다.
function longRunningTask() {
console.log('작업 끝');
}
console.log('시작');
setTimeout(longRunningTask, 0);
console.log('다음 작업');
싱글 스레드
이벤트 기반, 논블로킹 모델과 더불어 노드를 설명할 때 자주 나오는 용어가 하나 더 있다.
싱글 스레드란 스레드가 하나뿐이라는 것을 의미한다.
스레드를 이해하기 위해서는 프로세스부터 알아야 한다.
- 프로세스는 운영체제에서 할당하는 작업의 단위이다.
- 노드나 웹 브라우저 같은 프로그램은 개별적인 프로세스이다.
- 프로세스 간에는 메모리 등의 자원을 공유하지 않는다.
- 스레드는 프로세스 내에서 실행되는 흐름의 단위이다.
- 프로세스는 스레드를 여러 개 생성해 여러 작업을 동시에 처리할 수 있다.
- 스레드들은 부모 프로세스의 자원을 공유한다.
노드를 실행하면 먼저 프로세스가 하나 생성되고, 프로세스에서 스레드들을 생성하는데, 이때 내부적으로 스레드를 여러 개 생성한다.
그중에서 직접 제어할 수 있는 스레드는 단 하나뿐이며, 흔히 노드가 싱글 스레드라고 여겨지는 것이다.
여러 개의 일을 동시에 처리할 수 있으므로 멀티 스레드가 싱글 스레드보다 좋아 보일 수 있다.
ex) 음식점에 점원이 한 명이 있고, 손님은 여러 명일 때
점원 한 명이 주문을 받아 주방에 넘기고, 주방에서 요리가 나오면 손님에게 서빙을 한다. 그 후 다음 손님의 주문을 받는다.
이런 구조라면, 다음 손님은 이전 손님의 요리가 나올 때까지 아무것도 하지 않고 기다리고 있어야 한다.
이것이 바로 싱글 스레드(점원), 블로킹 모델이다.
이번에는 점원이 한 손님의 주문을 받고, 주방에 주문 내역을 넘긴 뒤 다음 손님의 주문을 받는다.
요리가 끝나기까지 기다리는 대신, 주문이 들어왔다는 것만 주방에 계속 알려주는 것이다.
주방에서 요리가 완료되면 완료된 순서대로 손님에게 서빙한다.
요리의 특성에 따라 완료되는 순서가 다를 수 있으므로, 주문이 들어온 순서와 서빙하는 순서가 일치하지 않을 수 있다.
이것이 싱글 스레드, 논블로킹 모델이다.
멀티스레드 방식에서는 손님 한 명이 올때마다 점원도 한 명씩 붙어 주문을 받고 서빙한다.
언뜻 보면 싱글 스레드보다 좋은 방법처럼 보이지만, 장단점이 있다.
일단 손님 한 명당 점원도 한 명이면 서빙 자체는 걱정이 없고, 점원 한 명에게 문제가 생겨도 다른 점원으로 대체하면 되기 때문이다.
하지만 손님의 수가 늘어날 수록 점원의 수도 늘어난다.
손님 수가 줄어들었을 때 일을 하지 않고 노는 점원이 있다는 것도 문제가 되며, 점원을 새로 고용하거나 기존 직원을 해고하는 데는 비용이 발생한다.
'자바스크립트 > NodeJS' 카테고리의 다른 글
[NodeJS] 중요한 내장 객체 알아보기 (0) | 2023.06.27 |
---|---|
[NodeJS] 모듈 (0) | 2023.06.26 |