JavaScript는 왜 싱글 스레드로 설계되었을까?
JavaScript는 태생적으로 싱글 스레드(single-threaded) 언어입니다. 하나의 스레드에서 코드를 순차적으로 실행하며, 이를 가능하게 하는 핵심 메커니즘이 바로 이벤트 루프(Event Loop)입니다.
처음 JavaScript가 만들어질 당시에는 브라우저에서 간단한 사용자 인터랙션을 처리하는 것이 주 목적이었기 때문에, 복잡한 멀티스레드 모델보다는 단순하고 예측 가능한 실행 모델이 훨씬 적합했습니다.
이벤트 루프(Event Loop)의 동작 원리
이벤트 루프의 역할은 매우 단순합니다. 바로 콜 스택(Call Stack)과 콜백 큐(Callback Queue)를 끊임없이 감시하는 것입니다.
동작 방식은 다음과 같습니다.
- 콜 스택이 비어 있는지 확인합니다.
- 콜 스택이 비어 있으면, 콜백 큐에서 가장 앞에 있는 이벤트(콜백 함수)를 꺼냅니다.
- 해당 콜백을 콜 스택에 푸시(push)하여 실행합니다.
이 과정을 무한히 반복하면서 비동기 작업의 결과를 순서대로 처리하기 때문에, 단일 스레드임에도 불구하고 사용자 경험에는 거의 병목이 발생하지 않습니다.
99.999%의 웹사이트에는 멀티스레딩이 필요 없다
브라우저 환경의 JavaScript가 이벤트 루프 차원에서 멀티스레딩을 지원하지 않는 이유는 의외로 단순합니다. 대부분의 웹사이트에서는 필요하지 않기 때문입니다.
DOM 조작, 이벤트 처리, 간단한 비동기 통신 등 일반적인 웹 페이지의 작업은 이벤트 루프만으로도 충분히 매끄럽게 처리됩니다. 오히려 멀티스레딩을 도입하면 여러 스레드가 동시에 DOM에 접근할 때 발생할 수 있는 경쟁 상태(race condition)와 같은 복잡한 문제를 다뤄야 하므로, 언어와 브라우저 구현이 훨씬 어려워집니다.
멀티스레딩이 필요하다면? 웹 워커(Web Workers) 활용하기
물론 무거운 연산이나 대용량 데이터 처리처럼 진짜 병렬 처리가 필요한 경우도 있습니다. 이럴 때를 위해 브라우저는 웹 워커(Web Workers)라는 해결책을 제공합니다.
웹 워커는 웹 콘텐츠가 백그라운드 스레드에서 스크립트를 실행할 수 있도록 해주는 간단한 수단입니다. 주요 특징은 다음과 같습니다.
- UI 비차단: 워커 스레드는 메인 스레드와 분리되어 작업을 수행하므로, 사용자 인터페이스(UI)를 방해하거나 화면을 멈추게 하지 않습니다.
- I/O 처리 가능: XMLHttpRequest를 통해 네트워크 I/O를 수행할 수 있습니다(단, responseXML과 channel 속성은 항상 null로 반환됩니다).
- 메시지 기반 통신: 워커가 생성되면, 생성한 코드가 지정한 이벤트 핸들러로 postMessage()를 통해 메시지를 주고받으며 서로 통신할 수 있습니다. 물론 반대 방향의 통신도 가능합니다.
즉, 워커 내부에서 시간이 오래 걸리는 계산을 실행하더라도 메인 스레드는 계속해서 사용자 입력과 렌더링을 처리할 수 있습니다. 다만 워커는 DOM에 직접 접근할 수 없고, 스레드 간 데이터 교환이 메시지 전달 방식으로만 이루어진다는 점은 유의해야 합니다.
정리
JavaScript가 멀티스레딩을 기본으로 지원하지 않는 것은 기술적 한계라기보다 웹이라는 환경에 최적화된 설계 선택입니다. 이벤트 루프 덕분에 단일 스레드로도 대부분의 작업을 효율적으로 처리할 수 있으며, 정말 병렬 처리가 필요한 경우에는 웹 워커를 통해 백그라운드 스레드를 활용하면 됩니다. JavaScript의 동작 모델을 깊이 이해하고 싶다면, 이벤트 루프와 웹 워커의 동작 방식을 직접 실습해 보는 것을 추천합니다.