-
Node.js-Event-Driven개발언어/Node.js 2022. 7. 9. 15:42
Event-Driven
NodeJs Runtime환경에 작성한 SourceCode를 동작하게하면, 실제로는 NodeJsApp형태로 App레벨에서 동작하게 된다. App안에는 동적으로 생성한 data를 보관하는 Memory Heap이라는 곳이 있고, 또 작성한 code의 함수의 순서를 정확하게 기억하는 CallStack이 있다.
CallStack은 main()이라는 함수를 처음으로 호출하고, main()함수를 수행하다가 main()안에서 중간에 first()를 호출하고 또 first()를 수행하다가 중간에 second()를 호출하면, 순서대로 Call Stack이 쌓이게 되고, second() 끝나면 다시 first()에서 second() 호출한 시점으로 돌아간다. second()함수에서 return한 값이 있다면, 그 값을 이용해서 first()함수를 처리하고, first()함수가 끝나면, main()함수에서 first()함수를 호출했던 시점으로 돌아가게 된다. 이런 Call Stack을 활용해서 함수가 호출된 순차적인 흐름을 기억할 수 있고, 특정함수가 끝나면 다시 어디로 돌아가야 하는지 알 수 있다.
예를 들어서 second()함수에서 NodeJS의 API중 하나인 setTimeout()을 3초 뒤에 어떤일을 할 수 있도록 호출했다고 가정하자 만약 blocking 환경이라면 3초를 기다렸다가 원하는 동작을 수행하겠지만 NodeJS에서는 nonblocking driven방식이기 때문에 NodeJS에게 지정한 3초가 지나면, callback을 던져달라고 요청한 다음에 바로 다음으로 넘어 가게된다. 그러면 NodeJS 내부에서 타이머를 시작하고, Call Stack은 다시 순차적으로 함수를 실행하게 된다. 그래서 시간이 다 완료가 되면, NodeJS는 호출해야되는 callback을 Task Queue라는 대기 줄에다가 넣어준다.
App과 NodeJS API를 연결해주는 중요한 요소가 존재한다. => Event Loop
Event Loop
Event Loop는 우리의 Call Stack이 비어 있을때까지 기다렸다가 CallStack이 비어 있으면, Task Queue에 들어 있던 callback 함수를 Call Stack으로 가져오게 된다. callback함수는 App내부에서만 사용할 수 있기 때문에 Task Queue에 있는
callback함수를 App내부의 Call Stack으로 가져오게 된다. Call Stack에 callback 함수가 들어오면, callback함수를 수행하게된다.
정리
JavaScript로 만들어진 App은 Single Thread이지만, NodeJS API 즉 NodeJS Runtime Environment은 Milti Thread환경이 가능하기 때문에 이렇게 필요한 환경을 던져주면, 알아서 병렬적으로 처리가 되다가 원하는 동작이 다 완료가 되면 즉 완료되는 event가 발생하면, 등록 받은 callback함수를 Task Queue에 던져준다.
그리고 Task Queue와 App을 연결해주는 Event Loop라는 것이 Task Queue에 있는 callback을 Call Stack이 비어있을 때 하나하나씩 가져오게 된다.
NodeJS APP은 Main Single Thread가 있다. 즉 App에서 필요한 일들을 처리하는 Main Thread가 있고, file을 읽고 쓰고 요청하고 이런 일들은 NodeJS가 제공해주는 API를 통해서 할 수 있고, 이때 event가 발생할 때 처리해야하는 것을 callback 형태로 전달해주면, NodeJS 내부적으로 병렬적으로 처리해준다.
V8
JavaScript Engine(C++)
Libuv
Non-Blocking I/O(file system,DNS,network,streaming)
Libuv라이브러리를 통해서 Non-blocking I/O가 가능하다. 즉 이 라이브러리는 file을 읽고 쓰거나 network를 읽고 쓰거나이런 일들을 비동기적으로 할 수 있도록 지원해주는 라이브러리이다. 그리고 이 라이브러리는 위도우냐 리눅스냐 이런각각의 운영체제별로 각각이 맞게 처리를 해주기때문에 해당 라이브러리에서 제공하는 API만 이용해도 운영체제가 달라지는 것에대해서 전혀 걱정하지 않고, 하나의 API를 사용할 수 있다. Libuv라이브러리가 비동기 적으로 읽고 쓰는 일을 담당하고 있다.
LLhttp(TypeScript,C)
HTTP를 parsing하는 라이브러리 module
Open SSL(C)
crypto와 tls를 담당하는 모듈
c-ares
asyn DNS request
DNS를 요청하는 모듈
zlib
compression and decompression
data를 압축하고 압축을 해제할 수 있는 모듈
이외에도 NodeJs를 동작하기위한 모듈들을 NodeJS 자체에서 가지고 있다.
Non-Blocking I/O방식과 Event-Driven 방식 덕분에 우리가 원하는 것을 callback형태로 처리할 수 있다. 여기서 정말 중요한 point는 바로 NodeJS App은 Main Single Thread에서 동작한다. 즉 우리가 등록한 callback함수는 결국 App에서 동작하기 때문에(Single Thread에서 동작한다는 의미) callback함수에서 무언가 무거운 일을 처리하게 되면 결국은 그 무거운 일이 다 끝날때 까지 다음 Call Stack으로 넘어가지 않는다.
그래서 App과 callback함수로 전달되는 code는 가벼운일들만 처리해야 한다. callback함수에서 무한으로 forloop를 돌리거 나 무거운 계산을 하면 안된다.
NodeJs는 I/O관련된 일에는 NO.1 왜냐하면 Non-Blocking I/O방식과 Event-Driven 방식으로 되어 있기 때문이다. 오히려 multithreading방식보다 더 효율적으로 I/O 처리를 담당할 수 있다.
하지만 CPU관점에서는 NodeJS는 적합하지 않을 수 있다. NodeJS 자체는 Single thread로 작동하기 때문에 무거운 계산을 하는 일들에는 적합하지 않다.
하지만 NodeJS12 version 이상부터는 worker threads라는 thread를 만드는 Api를 활용할 수 있다. 그래서 image resize 혹은 video encoding 같은 무거운 계산(heavy calculation)을 해야하는 경우 worker threadsfmf 활용해 볼 수 있다. 하지만 멀티 쓰레드는 조심해서 사용해야 한다. 쓰레드를 지나치게 많이 만들면, 그 자체만으로 메모리를 많이 잡아 먹게되고 성능에 영향을 끼칠 수 있기 때문에 조심해서 사용하는 것이 좋다.
'개발언어 > Node.js' 카테고리의 다른 글
NodeJS Timer와 Call Stack의 연관 (0) 2022.07.09 NodeJS Process 정보 (0) 2022.07.09 NodeJS 4가지 특징 (0) 2022.07.09 NodeJS 운영체제 정보 (0) 2022.07.08 NodeJS 최신 Module(export/import) (0) 2022.07.08