JavaScript는 싱글 스레드 언어라는 말을 많이 들어보았을 것이다. 하지만 자바스크립트는 웹 브라우저나 NodeJS와 같은 멀티 스레드 환경에서 실행된다. 싱글스레드인 자바스크립트가 어떻게 멀티 스레드 처럼 비동기적으로 실행되는지 알아보고자 한다.
0. JS Engine
JavaScript 엔진은 자바스크립트 코드를 해석하고 실행하는 인터프리터이고, 브라우저마다 다른 엔진을 사용한다.
그중에서도 가장 대표적인 엔진으로는 Google의 V8 엔진이다. 가장 많이 사용되는 Chrome과 Node.js에서 상요되는 엔진이기도 하기 때문이다.
- Chrome : V8
- FireFox : SpiderMonkey
- Safari : Webkit
- Explorer, Edge : Chakra
Javascript는 싱글 스레드 언어이다.
"싱글 스레드"라는 말은 자바스크립트 엔진이 단일 호출 스택 = 하나의 Call Stack을 사용한다. 라는 뜻이다.
JavaScript 엔진은 크게 Memory Heap과 Call Stack으로 이루어져 있다.
- Memory Heap : 메모리 할당이 일어나는 장소
- Call Stack : 코드가 실행될 경우 하나씩 stack의 형태로 쌓이는 장소
그런데, 웹 애플리케이션에서는 네트워크 요청이나 이벤트 처리, 타이머와 같은 작업을 멀티로 처리해야 하는 경우가 많다. 따라서 오래 걸리고 반복적인 작업들은 자바스크립트 엔진이 아닌 브라우저 내부의 멀티 스레드인 Web APIs에서 비동기 + 논블로킹으로 처리된다. 비동기 + 논블로킹(Async + Non blocking)Visit Website는 메인 스레드가 작업을 다른 곳에 요청하여 대신 실행하고, 그 작업이 완료되면 이벤트나 콜백 함수를 받아 결과를 실행하는 방식을 말한다.
이러한 싱글 스레드인 JavaScript의 작업을 멀티 스레드로 작업을 동시에 처리시키게 하던가, 또는 여러 작업 중 어떤 작업을 우선으로 동작시킬 것인지 결정하는 세심한 컨트롤을 하기 위해 존재하는 것이 바로 이벤트 루프(Event Loop) 이다.
💡 비동기로 동작하는 핵심요소는 자바스크립트 언어가 아니라 브라우저라는 소프트웨어가 가지고 있다.
1. Event Loop : 브라우저 동작을 제어하는 관리자
Event Loop의 역할:
- 비동기 함수 작업을 Web API에 옮기는 역할
- 작업이 완료된 콜백을 큐(Queue)에 적재
- Call Stack이 빈 상태가 되면 자바스크립트 엔진에 적재
따라서 이벤트 루프는 Call Stack에 현재 실행 중인 작업이 있는지 그리고 Callback Queue에 대기 중인 작업이 있는지 반복적으로 확인하는 일종의 무한 루프만을 돌고, (그래서 이벤트 '루프' 이다)
일종의 '작업을 옮기는 역할' 만을 한다. 작업을 처리하는 주체는 자바스크립트 엔진과 웹 API 이다.
2. Web API
그림의 오른쪽에 있는 Wep API는 JS Engine의 밖에 그려져 있다. 즉, 자바스크립트 엔진이 아니다.
Web API 는 브라우저에서 제공하는 API 로, DOM, Ajax, Timeout 등이 있다. Call Stack에서 실행된 비동기 함수는 Web API를 호출하고, Web API는 콜백함수를 Callback Queue에 밀어 넣는다.
Web APIs는 타이머, 네트워크 요청, 파일 입출력, 이벤트 처리 등 브라우저에서 제공하는 다양한 API를 포괄하고 있다. Web API는 브라우저에서 멀티 스레드로 구현되어 있다. 그래서 브라우저는 비동기 작업에 대해 메인 스레드를 차단하지 않고 다른 스레드를 사용하여 동시에 처리할수 있는 것이다.
Web APIs의 대표적인 종류
- DOM : HTML 문서의 구조와 내용을 표현하고 조작할 수 있는 객체
- XMLHttpRequest : 서버와 비동기적으로 데이터를 교환할 수 있는 객체 AJAX기술의 핵심
- Timer API : 일정한 시간 간격으로 함수를 실행하거나 지연시키는 메소드들을 제공
- Console API : 개발자 도구에서 콘솔 기능을 제공
- Canvas API : <canvas> 요소를 통해 그래픽을 그리거나 애니메이션을 만들 수 있는 메소드들을 제공
- Geolocation API :웹 브라우저에서 사용자의 현재 위치 정보를 얻을 수 있는 메소드들을 제공
이때 오해하지 말아야 할 것은 모든 Web API들이 비동기로 동작되는 것이 아니다. Web API에는 동기적으로 처리되는 것과 비동기적으로 처리되는 것이 모두 있다. 예를 들어 DOM API나 Console API는 동기적으로 처리되고, XMLHttpRequest나 Timer API는 비동기적으로 처리된다.
3. Callback Queue
비동기적으로 실행된 콜백함수가 보관 되는 영역이다.
- Task Queue: setTimeout, setInterval, fetch, addEventListener 와 같이 비동기로 처리되는 함수들의 콜백 함수가 들어가는 큐 (macrotask queue 는 보통 task queue 라고 부른다)
- Microtask Queue: promise.then, process.nextTick, MutationObserver 와 같이 우선적으로 비동기로 처리되는 함수들의 콜백 함수가 들어가는 큐 (처리 우선순위가 높음)
같은 queue 안에 적재되는 콜백이라도 어떠한 비동기 작업이냐에 따라 우선순위가 다른 태스크들이 있을 수 있다.
→ Promise.then 결과가 setTimeout보다 우선 처리되는 것 처럼
ex) 해당 링크에서 [3.2.2. MicroTask Queue 처리 과정]을 참고해보자
4. JavaScript는 왜 Single Thread를 채택했을까?
- 웹 브라우저 환경:
- JavaScript는 원래 웹 브라우저에서 실행되도록 설계된 언어이다. 웹 브라우저는 사용자 인터페이스를 담당하므로, 동시성 문제를 최소화하는 것이 중요하다. 싱글 스레드 모델은 복잡한 동시성 문제를 줄여준다.
- 사용자 인터페이스와 관련된 동작에서, 두 개 이상의 스레드가 동일한 DOM을 동시에 조작할 경우 충돌이 발생할 수 있다. 싱글 스레드 모델은 이러한 충돌을 방지한다.
- 언어 설계의 단순성:
- 싱글 스레드 모델은 다중 스레드와 비교했을 때 구현이 더 단순하고 오류가 적다. 스레드 간의 데이터 공유와 같은 동기화 문제를 고려할 필요가 없으므로, JavaScript의 설계와 구현이 단순해진다.
- 초창기 웹의 요구사항은 현재와 달리 상대적으로 단순했기 때문에, 복잡한 멀티스레딩보다 단순한 모델이 적합했다.
- 성능과 안전성:
- 싱글 스레드 모델은 코드 실행의 예측 가능성을 높여준다. 모든 코드가 순차적으로 실행되므로, 디버깅이 상대적으로 쉬워진다.
- 멀티스레드 환경에서 발생할 수 있는 데드락, 레이스 컨디션 등의 문제를 방지할 수 있다.
JavaScript가 싱글 스레드 모델을 채택한 것은 웹 브라우저 환경의 특성과 초기 언어 설계의 단순성, 그리고 성능과 안전성 등의 이유에서 비롯된다. 싱글 스레드 모델은 동시성 문제를 최소화하고, 안정적이고 예측 가능한 코드를 작성할 수 있게 하며, 비동기 프로그래밍을 통해 이러한 한계를 극복할 수 있는 방법도 제공한다. 이러한 이유들로 인해, JavaScript는 여전히 싱글 스레드 모델을 유지하고 있으며, 개발자들에게 직관적이고 효율적인 프로그래밍 환경을 제공한다.
'JavaScript' 카테고리의 다른 글
[JavaScript] Full Calendar 속성 및 사용법 (0) | 2025.01.02 |
---|