노드는 정말 싱글스레드로 작동할까 ?
만약 노드를 모르거나 노드의 작동방식을 알고싶다면 this article을 참고하세요.
대부분의 노드 설명글은 노드를 single-threaded라고 소개합니다. 다음은 노드의 멀티쓰레드와 관련된 오해들입니다.
- 자바스크립트와 같이, 노드는 멀티스레딩을 지원하지 않는다.
- 노드는 자바처럼 멀티쓰레딩에 적합한 언어이다.
- 노드에는 두개의 쓰레드가 있으며, 하나는 이벤트루프에 대응하며 하나는 프로그램 실행을 담당한다.
이러한 것들은 오해라고 할 수 있는 가정들입니다. 사람들 종종 멀티쓰레딩과 멀티프로세싱, 쓰레드 풀, 그리고 운영체제가 어떻게 그것들을 관리하는지에 대해 헷갈릴 때가 있습니다.
- Process
- 프로세스는 실행중인 프로그램입니다. 즉 프로그램이 실행되면 프로세스가 생성됩니다. 프로세스 하나는 여러개의 쓰레드(multi-threads)를 가질 수 있습니다.
- threads
- 쓰레드는 프로그래밍 된 구문들의 가장 작은 시퀀스(순서?)로 운영체제의 스케줄러에 의해 독립적으로 관리됩니다.
- Multi-processing
- 한개의 컴퓨터에서 두개 이상의 CPUs를 사용하는 것입니다. 이제, 멀티프로세서가 사용가능함에 따라 여러개의 프로세스들이 한번에 실행 될 수 있습니다.
- Multi-threading
- 멀티 쓰레딩은 하나의 프로세스가 여러개의 Code block(쓰레드)를 가지고 프로세스의 context에 따라 동시에 실행되게 하는 실행 모델입니다.
- Thread Pool
- 쓰레드 풀은 미리 생성된 쓰레드 그룹입니다. idle(유후) 상태의 쓰레드들은 주어진 작업을 실행하기 위해 대기합니다. 쓰레드 풀을 유지함으로 프로그램의 성능이 향상되고 실행 지연을 막을 수 있습니다.
노드 어플리케이션은 싱글 스레드로 작동하며 이벤트루프 또한 같은 스레드에 존재합니다. 따라서 우리는 노드가 싱글스레드라 말하지만 주목할 점은 노드에는 싱글스레드가 아닌 라이브러리들이 존재합니다.
예제의 도움을 통해 이해해보도록 합시다.
const crypto = require("crypto");
const start = Date.now();
function logHashTime() {
crypto.pbkdf2("a", "b", 100000, 512, "sha512", () => {
console.log("Hash: ", Date.now() - start);
});
}
logHashTime();
logHashTime();
logHashTime();
logHashTime();
노드의 crypto 모듈을 불러온 뒤 pbkdf2 함수를 4번 호출해 소요된 시간을 표시하였습니다.
결과를 보면, 4개의 logHashTime
함수는 실행되는 시간이 거의 동일합니다. 왜 4개의 함수들이 거의 비슷한 시간안에 실행 될 수 있을까요?
노드는 내부적으로 libuv라는 라이브러리를 사용합니다. 이 라이브러리는 운영체제와 관련된 작업들(비동기 IO, Networking, concureency 등)을 다루는데 사용됩니다.
Libuv는 OS-related 작업들을 수행하기 위해 모든 CPU 코어를 활용함으로써 4개의 쓰레드를 가진 쓰레드 풀을 설정합니다.
이것은 코어당 하나의 쓰레드를 생성하는 결과를 낳습니다. 이런 과정을 통해 모든 4개의 쓰레드는 logHashTime
을 병렬적으로(in parallel) 실행합니다. 결과적으로 4개의 함수 실행에 비슷한 시간이 소요됩니다.
만약 두개의 CPU 코어밖에 없고, 많은 Operation을 수행하면 어떨까?
현재 컴퓨터에 4개의 코어가 있고, 앞의 코드를 logHashTime
을 5번 실행하게 되면, 첫번째 4개의 함수 실행 소요 시간과 5번쨰 소요 시간은 차이가 존재합니다.
Libuv는 4개의 쓰레드를 가진 쓰레드 풀을 생성한 후 logHashTime
을 4개의 CPU 코어에서 실행합니다. 쓰레드 중 하나라도 실행이 완료되면, 다섯번째 logHashTime
을 실행하게 됩니다. 따라서 처음 4개의 함수 호출과 다섯번째 호출은 소요시간 차이가 발생하게 됩니다.
Libuv 쓰레드 풀의 쓰레드 숫자를 변경하고 싶다면?
우리는 한 줄의 코드를 작성함으로써 libuv의 쓰레드 수를 조정할 수 있습니다.
process.env.UV_THREADPOOL_SIZE =5;
해당 코드를 작성한 뒤 코드를 실행하면, 5개의 logHashTime
함수 실행 소요 시간이 거의 동일해집니다. 어떻게 가능했을까요?
모든 5개의 쓰레드는 4개의 CPU 코어 위에서 logHashTime
을 실행하기 위해 노력합니다. 따라서 OS 쓰레드 스케줄러는 context swtiching의 도움을 받아 각 쓰레드가 실행을 끝내는 속도를 똑같이 맞춥니다.
Conclusion
우리는 노드는 싱글 스레드지만 백그라운드에서는 libuv 라이브러리의 도움을 받아 여러개의 쓰레드를 사용해 asynchronous한 코드를 실행한다는 것을 알 수 있습니다.
원본 글 https://medium.com/better-programming/is-node-js-really-single-threaded-7ea59bcc8d64