자바스크립트(JavaScript)는 대표적인 싱글 스레드(Single Thread) 언어입니다. 즉, 한 번에 하나의 작업만 처리할 수 있는 구조를 가졌습니다. 그럼에도 불구하고 브라우저 환경에서 동시적으로 여러 작업을 수행하는 것처럼 보이는 이유는 자바스크립트의 이벤트 루프(Event Loop)와 비동기 처리 모델 덕분입니다. 본 글에서는 자바스크립트가 싱글 스레드임에도 어떻게 논블로킹 방식으로 여러 작업을 효율적으로 처리하는지 그 원리를 설명하고, 실무와 면접 대비에 도움이 되는 개념 정리를 제공합니다.
자바스크립트는 왜 싱글 스레드인가?
자바스크립트는 초기 설계부터 UI와 사용자 입력을 안전하게 다루기 위해 싱글 스레드 모델로 만들어졌습니다. 스레드가 여러 개일 경우, DOM을 동시에 수정하면서 충돌이나 레이스 컨디션(Race Condition)이 발생할 수 있기 때문입니다. 따라서 브라우저는 JS를 한 줄씩 순차적으로 처리하는 단일 실행 컨텍스트를 기반으로 작동합니다.
그렇다면 네트워크 요청, 타이머, 이벤트 리스너 등 동시성 작업은 어떻게 가능할까요? 이는 자바스크립트 자체가 아닌 브라우저(Web API)와 이벤트 루프 구조 덕분입니다. 자바스크립트 엔진(V8 등)은 실행 컨텍스트와 콜 스택(call stack)만 처리하고, 나머지 비동기 작업은 브라우저가 자체적으로 처리한 후, JS 런타임에게 콜백을 다시 전달하는 방식으로 동작합니다.
스레드 수는 1개지만, 비동기 구조를 통해 동시성(Concurrency)을 구현하는 구조가 바로 자바스크립트의 핵심입니다.
이벤트 루프와 비동기 처리 구조의 핵심
자바스크립트의 비동기 처리 구조는 크게 콜 스택(Call Stack), Web API, 태스크 큐(Task Queue), 마이크로태스크 큐(Microtask Queue), 이벤트 루프(Event Loop)로 나뉘어 작동합니다.
1. 콜 스택(Call Stack)
자바스크립트는 동기 코드가 실행될 때, 실행되는 함수가 콜 스택에 쌓이고, 완료되면 빠져나갑니다. 이 스택이 비어 있어야 비동기 콜백이 실행될 수 있습니다.
2. Web API (브라우저 제공)
비동기 작업(ex. setTimeout
, fetch
, DOM 이벤트 등)은 콜 스택에서 실행되는 것이 아니라 브라우저의 Web API 영역에서 처리됩니다. 완료된 콜백은 큐에 등록됩니다.
3. 태스크 큐 vs 마이크로태스크 큐
- 태스크 큐:
setTimeout
,setInterval
등의 콜백을 보관 - 마이크로태스크 큐:
Promise.then
,MutationObserver
등이 저장됨. 우선순위가 높음
4. 이벤트 루프(Event Loop)
이벤트 루프는 끊임없이 콜 스택이 비었는지 확인하고, 비어 있다면 먼저 마이크로태스크 큐를 모두 처리한 후 태스크 큐에서 콜백을 가져와 실행합니다.
5. 실행 흐름 예시
console.log('Start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise');
});
console.log('End');
실행 결과:
Start → End → Promise → setTimeout
설명: Promise.then()
은 마이크로태스크 큐에 등록되어 setTimeout()
보다 먼저 실행됩니다.
6. 구조 요약
[JS 코드 실행]
↓
[Web API 위임] → 비동기 처리 (예: setTimeout, fetch)
↓
[콜백 등록 → 큐 저장]
↓
[이벤트 루프] → 스택이 비면 → 마이크로태스크 → 태스크 순으로 실행
실무에서의 적용과 면접 대응 전략
비동기 처리 구조는 실무에서 다음과 같은 문제 해결에 직접적으로 연관됩니다.
1. 블로킹 vs 논블로킹 예시
다음은 블로킹 코드의 대표적인 예입니다:
// 동기 처리 → 전체 코드가 멈춤
const result = longRunningFunction();
console.log('다음 코드 실행');
비동기 처리로 개선하면 다음과 같습니다:
longRunningFunctionAsync().then(result => {
console.log('비동기 처리 완료');
});
console.log('다음 코드 실행');
위와 같이 콜백, Promise, async/await를 통해 CPU 작업과 사용자 경험을 분리할 수 있습니다.
2. async/await 흐름 이해
async function fetchData() {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
}
위 코드는 동기처럼 보이지만 내부적으로는 Promise 기반 비동기 흐름입니다. await
는 Promise가 resolve될 때까지 기다리며, 실행 중 스레드를 블로킹하지 않습니다.
3. 면접 질문 예시
- “자바스크립트는 싱글 스레드인데 어떻게 여러 작업을 처리하나요?”
- “이벤트 루프를 설명해보세요.”
- “마이크로태스크와 태스크 큐의 차이는 무엇인가요?”
4. 면접 답변 팁
단순한 정의보다는 이벤트 루프의 흐름을 직접 설명하고 예시를 들면 설득력 있는 답변이 됩니다.
“자바스크립트는 싱글 스레드이지만, 브라우저의 Web API와 이벤트 루프 구조 덕분에 비동기 작업을 처리할 수 있습니다. 실행 중인 코드 외 작업은 Web API에서 처리되고, 완료되면 큐를 통해 콜백이 다시 전달되어 콜 스택이 비었을 때 실행됩니다.”
결론
자바스크립트는 싱글 스레드 언어이지만, 이벤트 루프, 큐, Web API 등을 활용하여 비동기 작업을 효율적으로 수행할 수 있는 동시성(Concurrency) 모델을 구현하고 있습니다. 이 구조 덕분에 사용자는 블로킹 없이 빠르게 반응하는 웹 애플리케이션을 경험할 수 있습니다.
실무에서는 async/await
, Promise
, setTimeout
등의 실행 순서를 정확히 이해하는 것이 중요하며, 면접에서도 이 구조의 흐름을 실제 코드 예제와 함께 설명할 수 있도록 연습해두는 것이 좋습니다. 싱글 스레드의 제약을 비동기 처리로 극복하는 자바스크립트의 구조는, 현대 웹 개발의 중요한 기초입니다.