JavaScript는 왜 싱글 스레드일까?

본 글은 ChatGPT를 참고하여 작성된 글입니다. 그렇다고 본인이 조사해서 작성한 부분이 없는 건 아님.
어떤 프로세스가 싱글스레드라는 말은, 한 번에 한 작업밖에 처리하지 못하고, 그것도 그 작업을 순서대로 (동기적으로) 처리할 수밖에 없다는 뜻이다. 그런데도 JavaScript는 왜 지금까지 싱글스레드 방식을 벗어나지 않았을까?
Personal Note: 아마… 로블록스 엔진이 싱글스레드인 이유와 일맥상통하지 않을까 싶다.

TL;DR

초기에 단순히 웹 브라우저를 위해 설계되었기도 하고, 이벤트를 통해 코드를 비동기적으로도 실행할 수 있었으며, TODO: 멀티스레드로 바뀌지 않았던 이유
이 글을 읽기에 앞서,
프로세스, 스레드 (process, thread)
동기 / 비동기 (sync / async)
블로킹 / 논블로킹 (blocking / non-blocking)
-에 대한 이해가 우선되어야 한다. 이미 아는 이는 스킵해도 좋다.

— 앞서 이해하기

스레드 ⊂ 프로세스 ⊂ 프로그램

프로세스

보조기억장치(HDD, SSD)에 저장되어 있던 프로그램이 주기억장치(RAM)으로 올라와 실행될 때, 그것을 프로세스라고 부른다. 하나의 프로그램은 여러 개의 프로세스를 가질 수 있으며, 그러한 방식을 멀티프로세스라고 한다. 이때 각 프로세스는 별도의 메모리 공간을 사용한다.

멀티프로세스는 언제?

한 프로세스가 실패해도 다른 프로세스에 영향을 주지 않아야 할 때.
각 프로세스가 서로 다른 권한이나 환경, 즉 다른 수준의 보안에서 실행되어야 할 때.

스레드

한 프로세스의 메모리 공간 내에서 동시에 실행되는 작업이다. 한 프로세스는 여러 개의 스레드를 포함할 수 있고, 이 방식은 멀티스레드이다. 이때 각 스레드는 주메모리 공간(code, data, stack, heap)을 공유한다.

멀티스레드는 언제?

빠른 통신이 필요할 때. (스레드 간의 *문맥 교환(context switch) 비용이 프로세스보다 적으므로.)
Context switch: 운영 체제(OS)에서 하나의 스레드가 실행 중인 상태(context)를 저장하고, 다른 스레드를 실행하는 과정.
작업을 분리하여 응답 시간을 줄여야 할 때.
데이터를 동시에 처리하면서 그 데이터의 동기화가 필요할 때.
TODO: JS에 멀티스레드가 필요한 이유

동기 / 비동기

동기
비동기

블로킹 / 논블로킹

블로킹
논블로킹
지금까지 프로세스와 스레드, 동기/비동기, 블로킹/논블로킹을 알아보았다. 그렇다면 다시 본론으로 돌아와서··· JavaScript는 왜 싱글 스레드일까?

— 언어적 관점

웹 브라우저 환경

JavaScript는 웹 브라우저를 위해 만들어진 언어였고, 초기 웹 브라우저는 싱글스레드였다.
TODO: 웹 브라우저가 싱글스레드였던 이유

이벤트 중심의 비동기 처리

싱글스레드 방식으로 실행되더라도, JS는 기반 엔진의 이벤트 루프(event loop)를 통해 작업을 비동기적으로 처리할 수 있다.
이벤트 루프는 타이머, 유저 상호작용 등의 이벤트를 감지하고, 그것에 반응하기 위해 계속 반복된다.
1.
소스 코드
console.log('Start'); setTimeout(function() { console.log('나는 setTimeout 함수 속 콜백 익명 함수야!'); }, 2000); // 2초 후에 콜백 함수 실행 console.log('End');
JavaScript
복사
2.
실행 결과
Start End 나는 setTimeout 함수 속 콜백 익명 함수야!
Plain Text
복사
메인 함수가 끝난 뒤에, setTimeout 함수 속의 *콜백(callback) *익명(anonymous) 함수가 비동기적으로 로그를 남겼다.
콜백 함수: 어떤 함수의 인자로 전달된 함수.
익명 함수: 함수명 없이 일회용으로 선언된 함수.
JS의 엔진에는 여러 가지가 있는데, 대표적으로는
Google(Chrome, Node.js)의 V8 - TODO: 8기통?
Mozilla(FireFox)의 SpiderMonkey
Apple(Safari)의 JavaScriptCore (Nitro)
Meta(React)의 Hermes
-가 있다. 이 글에서는 가장 점유율이 높은 Google의 V8엔진과 Node.js의 관점에서 이번 주제를 바라볼 것이다.

— Node.js적 관점

Node.js는 이벤트 중심, 비동기, 서버-사이드, JS 런타임 환경*이다. 점점 늘어나는 유저의 연결을 감당할 수 있는 (scalable) 네트워크 앱을 빌드하기 위해 고안됐다.
런타임 환경: 프로그램의 실행을 지원하는 시스템. 주로 컴파일러/인터프리터, 메모리 관리, I/O 처리를 포함한다.
This is in contrast to today's more common concurrency model, in which OS threads are employed. Thread-based networking is relatively inefficient and very difficult to use. Furthermore, users of Node.js are free from worries of dead-locking the process, since there are no locks. Almost no function in Node.js directly performs I/O, so the process never blocks except when the I/O is performed using synchronous methods of Node.js standard library. Because nothing blocks, scalable systems are very reasonable to develop in Node.js.
이 방식은 OS의 스레드를 활용하는 대세의 동시성 모델에 역행한다. (그렇지만) 스레드 기반의 네트워킹은 상대적으로 비효율적이고 굉장히 쓰기 어렵다. 거기다가, (메모리) 락이 없으므로 Node.js의 사용자는 프로세스가 교착 상태(deadlock)에 빠질까 봐 걱정하지 않아도 된다. Node.js에는 직접적으로 입·출력을 하는 함수가 거의 없으므로, Node.js 표준 라… 프로세스는
(저 번역 짱 잘하죠?)
TODO: 스레드 기반의 네트워킹이 비효율적인 이유

🪻———— 2차 초안 작성함 ————🪻

V8 엔진 기반

Node.js는 V8 JavaScript 엔진을 채택했고, V8 엔진은 싱글 스레드로 실행된다.

비동기 I/O 처리, 논블로킹

개념
설명
비동기 I/O 처리 (Asynchronic I/O Processing)
논블로킹 (Non-blocking)
때문에 단일 스레드에서도 수많은 요청을 잘 처리할 수 있다.

클러스터링을 통한 멀티프로세스

개념
설명
클러스터링
단일 Node.js 앱을 여러 프로세스로 나누어 멀티코어 시스템의 성능을 최대로 활용할 수 있도록 하는 방법.

— 결론

— 참고 자료

— TODO

다 쓰면 [텍스트](Notion문서 URL) 디코로 핑!
토글 (불렛 아님) → 문단
시간 남으면 컨텍스트 스위치 개념 더 자세하게
이해하지 않은 부분 삭제
const cluster = require('cluster'); const http = require('http'); const numCPUs = require('os').cpus().length; if (cluster.isMaster) { console.log(`Master ${process.pid} is running`); // 워커 프로세스 포크 for (let i = 0; i < numCPUs; i++) { cluster.fork(); } cluster.on('exit', (worker, code, signal) => { console.log(`Worker ${worker.process.pid} died`); }); } else { // 워커들은 HTTP 서버를 공유함 http.createServer((req, res) => { res.writeHead(200); res.end('Hello World'); }).listen(8000); console.log(`Worker ${process.pid} started`); }
JavaScript
복사