🍰

최이지

목차

JavaScript는 왜 싱글 스레드인가?

1. 자바스크립트란

웹 페이지를 대화식(예: 복잡한 애니메이션, 클릭 가능한 버튼, 팝업 메뉴 등)으로 만드는 데 사용되는 크로스 플랫폼, 객체 지향 스크립팅 언어

1-1.특징

비동기
동시성
논블로킹 I/O
스크립트 언어로 잘 알려져 있지만, 많은 비 브라우저 환경에서도 사용하고 있다. 프로토타입 기반, 다중 패러다임, 단일 스레드, 동적 언어로 객체지향형, 명령형, 선언형 스타일을 지원한다.
자바스크립트는 싱글스레드로 동작하는 언어이다. 메인 스레드, 하나의 스레드로 구성되어 있기 때문이다.
그렇다면, 싱글 스레드란 무엇일까?

1-2. 스레드와 싱글 스레드

싱글스레드
한 번에 하나의 일만 수행할 수 있는 것을 의미
스레드
프로세스 내에서 실행되는 실행 단위
프로세스
컴퓨터에서 실행 중인 프로그램의 인스턴스
싱글스레드란 한 번에 하나의 작업만 수행하는 것을 의미합니다. 다른 작업이 중간에 끼어들 수 없고, 기존에 수행하던 작업이 끝나야만 그 다음 작업을 수행할 수 있다.
기존 스레드 방식관 어떤 점이 다를까?

1-3. 싱글스레드와 멀티 스레드

싱글 스레드
하나의 프로세스가 한 번의 일만 처리
단순한 작업 처리에 효율적
동기적으로 작업을 처리함
구현 및 디버깅이 비교적 단순
멀티 스레드
복잡한 동기화 매커니즘 필요
비동기적 작업 처리
스레드 간의 자원 공유로 경쟁 상태나 데드락 발생
스레드 안전 고려

1-4. 싱글 스레드의 장단점

장점
단점
단순한 프로그래밍 모델
성능 한계
경쟁 상태와 데드락 방지
블로킹 문제
일관된 사용자 경험
제한된 동시성 처리
자원 효율성
응답성 저하
디버깅과 유지보수 용이

2. 동작 과정

자바스크립트를 실행하기 위해서는 자바스크립트 엔진이 필요하다. 대표적으로는 구글의 V8을 사용한다. V8은 Chrome & Node.js 내부에서 사용된다.
JavaScript의 엔진은 메모리힙(memory heap)과 콜스택(call stack)으로 구성되어 있다.
메모리 힙
메모리 할당이 발생하는 곳
Call Stack
코드가 실행될 때 스택 프레임이 있는 곳
메모리 힙이란 변수 선언, 함수 저장, 호출 등의 동적으로 할당된 메모리를 저장하는 영역이다. 자바 스크립트에선 가비지 컬렉터를 사용하여 더 이상 필요 없는 메모리를 해제한다.
( ex) cont num1 = 486; 이란 코드는 num1이라는 변수에 메모리 공간을 할당하고 486이라는 값을 저장하는 것.)
콜 스택은 후입선출(LIFO) 구조로 함수 호출을 관리합니다. 함수가 호출될 때마다 콜스택에 그 함수의 실행 컨텍스트가 쌓이고, 함수가 종료되면 콜스택에서 제거.
function mul(x,y){ return x*y; } fucntion print(x){ const mm = mul(x,x); console.log(mm); } print(5);
JavaScript
복사
위 코드가 있다고 가장했을 때, 이 코드를 실행하면 콜 스택이 비워진다. 이후 과정은 사진을 보면 이해할 수 있다.
1) 비어있는 콜스택에 가장 마지막 작업이 할당된다(print(x))
LIFO이기때문에
2) 제일 먼저 해야 될 작업이 할당된다.(mul(x,x))
3) 작업 수행 후 그 다음 작업이 스택에 할당된다.
4) 그 다음 작업을 순차적으로 수행하고 마지막 작업까지 수행하고 나면 스택이 비어지게 된다.
스택 프레임 : 콜 스택의 각 단계
특히 자바스크립트에서는 스택을 초과하게 되면 Maximum call stack size 에러가 발생하게 된다.
또한 콜 스택은 정해진 스택 사이즈가 존재하기 때문에, 콜 스택의 용량을 초과하게 되면 Stack Overflow가 발생한다.
자바스크립트는 싱글 스레드 기반 언어이기 때문에 다른 작업을 수행하려면 끝날 때까지 기다려야 한다.
이를 해결하기 위해서는 비동기 콜백으로 처리해야하는데, 이를 해결하기 해준 것이 자바스크립트 런타임이다.

런타임

런타임이란 자바스크립트 코드를 실행하는 환경을 의미한다. 이 환경에선 여러 구성 요소로 코드가 실행되고 동작하는 데 필수적인 기능을 제공한다.
런타임은 오랜 시간이 걸리는 작업들은 백그라운드에서 처리하고 간단하게 처리할 수 있는 작업들만 콜 스택에서 처리할 수 있는 기능이 있다.
1.
Web API
API란
브라우저 환경에서 제공되는 API들로 코드에서 비동기 작업을 수행할 수 있게 해주는 것. 주로 DOM 조작, HTTP 요청, 타이머 설정 등을 담당
2.
이벤트 루프(Event Loop)
자바스크립트의 비동기 처리 매커니즘을 담당하는 핵심 매커니즘. 콜 스택과 CallbackQueue를 모니터링하여, 콜 스택이 비어 있을 때 콜백 함수를 실행
3.
Callback Queue
비동기 작업의 완료된 콜백 함수들이 순서대로 대기하는 큐. Web API를 통해 비동기 작업이 완료되면, 해당 작업의 콜백 함수는 Callback Queue에 추가.
런타임의 동작 과정
1.
코드 실행
2.
메모리 할당
3.
함수 호출
4.
비동기 작업 처리
5.
이벤트 루프 실행

3. Node.js 동작원리

Node.js란 무엇일까?
JavaScript를 브라우저 밖에서도 실행할 수 있도록 하는 Javascript의 런타임. 확장성 있는 네트워크 애플리케이션을 만들 수 있도록 설계 런타임 : 특정 언어로 만든 프로그램을 실행할 수 있는 환경
비동기(Asycnronous)
이벤트 주도(Event-driven)
Non-Blocking I/O
확장성
Node.js는 싱글 스레드로 동작한다. 위의 스레드와 멀티스레드 차이점에서 설명했듯이 싱글 스레드는 프로세스 내에서 하나의 스레드가 하나의 요청(작업)만을 수행한다. 해당 요청이 수행될 때 다른 요청을 함께 수행할 수 없는 것이다.
이를 싱글스레드 블로킹 모델이라고 한다. 진행되고 있는 요청이 예정되어 있는 요청을 블로킹하기 때문이다.
그럼 이제 동작을 이해하기 위해 Node.js의 구조를 알아보자.

Node.js의 구조

Node.js는 JavaScript와 C++언어로 구성되어 있다. V8엔진도 70% 이상의 c++로 구성되어 있으며, libuv는 100%의 c++로 구성되어 있다.
Node.js의 구조는 내장 라이브러리와 v8엔진 그리고 libuv로 구성되어 있다. Node.js의 특성인 이벤트 기반, 논블로킹 I/O 모델들은 모두 libuv 라이브러리에서 구현된다.
Node.js에서 작성되는 거의 모든 코드들은 콜백함수로 이루어져 있다. 콜백함수들은 libuv 내에 위치한 이벤트 루프에서 관리 및 처리된다.
이벤트 루프는 여러 개의 페이즈(phase)들을 갖고 있고, 해당 페이즈들은 각자만의 강식으로 큐(Queue)를 갖는다. 라운드 로빈 방식으로 일정 규칙에 따라 여러 개의 페이즈들을 계속 순회한다. 해당 큐들은 FIFO 순서로 콜백 함수들을 처리한다. (콜스택과 반대되는 개념)
또한 비동기 I/O를 통해 파일 시스템 접근, 네트워크 요청 등과 같은 작업을 비동기로 처리한다.

이벤트 루프 동작 원리

1.
Timers : ‘setTimeout’과 ‘setInterval’에 의해 예약된 콜백 실행
2.
I/O callbacks : 일부 시스템 작업의 콜백 실행
3.
idle, prepare : 내부적으로 사용
4.
check : ‘setImmediate’ 콜백 실행
5.
close callbacks : ‘close’ 이벤트가 발생한 소켓 등의 콜백을 실행
비동기 처리 매커니즘
Node.js는 비동기 작업을 통해 싱글 스레드 환경을 효율적으로 활용한다. 주된 비동기 처리 매커니즘은
콜백 함수
프로미스(Promise)
async/await
등이 있다.

동작 과정

1.
애플리케이션 시작
애플리케이션이 시작되면 V8 엔진이 자바스크립트 코드를 읽고 실행
2.
모듈 로딩
필요한 모듈이 로딩된다. require() 함수를 통해 모듈을 가져온다
3.
이벤트 루프 시작
애플리케이션이 시작되면 이벤트 루프가 시작된다. 이벤트 루프는 콜 스택과 이벤트 큐를 지속적으로 모니터링한다.
4.
비동기 작업 요청
비동기 작업이 요청되면, libuv를 통해 백그라운드에서 처리하고, 완료되면 콜백 함수를 이벤트 큐에 추가한다.
5.
콜 스택 비어 있음 확인
이벤트 루프는 콜 스택이 비어있으면 이벤트 큐에서 콜백 함수를 가져와 콜 스택에 추가하고 실행한다.
6.
콜백 함수 실행
콜백 함수가 콜 스택에 추가되면, V8 엔진이 해당 함수를 실행한다. 비동기 작업이 완료될 때까지 이벤트 루프는 계속해서 콜 스택과 이벤트 큐를 모니터링

4. 왜 싱글 스레드일까

진짜 싱글 스레드일까?

자바스크립트의 메인 스레드인 이벤트 루프는 싱글 스레드이다. 따라서 자바스크립트는 싱글 스레드 언어라고 부른다. 하지만 이벤트 루프만 독립적으로 실행되지 않고 웹 브라우저나 Node.js와 같은 멀티 스레드 환경에서 실행된다. 결론적으로, 자바스크립트 자체는 싱글 스레드지만, 자바 스크립트 런타임은 싱글 스레드가 아니다.

자바스크립트는 왜 싱글 스레드로 동작할까?

1. 시초
자바스크립트는 1995년 브렌던 아이크(Brendan Eich)에 의해 브라우저 내에서 실행되는 간단한 스크립트 언어로 무려 10일만에 개발됐다. 초기 목표는 동적인 기능을 추가하는 것이였기에 Brendan Eich의 설계 목적은 두가지로 나뉘었다.
간단한 사용
빠른 개발
2.
웹 브라우저와의 통합
웹 브라우저는 사용자와 실시간으로 상호작용 해야 한다. 자바스크립트는 브라우저에서 실행되기 때문에 싱글 스레드 모델이 유리했다.
UI 업데이트 & 이벤트 처리
동기화 문제 회피
다중 스레드 환경에서는 여러 스레드가 동시에 DOM을 변경하려고 할 때 동기화 문제가 발생할 수 있다. 이 문제를 해결하기 위해 동기화 로직이 필요하지만, 싱글 스레드에선 이러한 문제가 발생하지 않는다.
3.
비동기 처리 매커니즘
자바 스크립트는 비동기 작업을 통하여 싱글 스레드 환경을 활용한다. 따라서 이벤트 루프(Event Loop)로 작업을 관리한다.
비동기 작업이 완료되면 콜백 함수가 큐에 추가되고, 이벤트 루프가 콜 스택이 비어 있을 때 이를 처리한다.
또한 콜백 큐에서 대기 중인 함수를 가져와 실행하고, 브라우저에서 제공하는 Web API를 통해 비동기 작업을 실행한다.

결론

자바 스크립트가 싱글 스레드로 동작하는 이유는 설계의 단순성과 효율성, 웹 브라우저와의 통합, 비동기 처리 매커니즘 때문이다. 싱글 스레드는 동기화 문제를 회피하고 예측가능한 UI 업데이트를 보장하여 자바스크립트 엔진의 성능을 최적화 할 수 있게 한다.
결과론적으로 자바스크립트 자체는 싱글 스레드 언어이지만, 웹 브라우저나 Node.js같은 멀티 스레드 환경에서 실행되므로 자바 스크립트 런타임은 멀티 스레드 환경을 활용한다.
이를 통해 동기화 문제를 극복하고 비동기 작업을 효율적으로 처리할 수 있다. 이벤트 루프와 콜백 큐를 사용해 비동기 작업을 관리하고, Web API를 통해 비동기 작업을 실행한다.
현재 교내 동아리 사이트 개발 프로젝트를 진행하면서 JS가 단순히 웹사이트의 기능을 구현하는 정도로밖에 이해를 하지 못했었다. 하지만 JS가 어떻게 동작할까? 라는 의구심을 품게 됐고 글을 써가며 JS의 동작 과정과 비동기적 처리 방법에 대해 설계의 이유를 깨닫게 됐다. 다음 글에서는 REST API에 대해 다뤄보는 글을 작성할 예정이다.

참고자료