그저 내가 되었고

⚡️JS:: 자바스크립트는 어떻게 동작할까? 본문

개발/JavaScript

⚡️JS:: 자바스크립트는 어떻게 동작할까?

hyuunii 2022. 12. 13. 23:56

 자바스크립트라는 싱글 쓰레드 언어라 했다. 한 번에 하나의 작업만 수행할 수 있다는 뜻. 그렇다면 JS를 주로 사용하는 웹사이트에서는 어떻게 여러 요청을 한번에 받을 수 있을까? 그리고 여러 요청이 오갈 수 있는 자바스크립트는 왜 싱글 쓰레드일까? 일단 자바스크립트 엔진부터 시작해서 천천히 알아보자.

 

자바스크립트 엔진

자바스크립트 엔진의 대표적인 예는 Google V8 엔진이다. V8 은 Chrome과 Node.js에서 사용한다. 아래는 엔진의 구조도를 간단히 나타낸 그림.

엔진의 주요 두 구성요소는 아래와 같다.

  • Memory Heap(메모리 힙) : 메모리 할당이 일어나는 곳. 프로그래머가 직접 할당/해제하는 메모리 영역. 힙 영역은 메모리의 낮은 주소부터 높은 주소까지 올라가는 절차 형식으로 할당됨.
  • Call Stack(호출 스택) : 코드 실행에 따라 호출 스택이 쌓이는 곳(자바스크립트에서 수행해야 할 함수들을 순차적으로 스택에 담아 처리) 만약 함수를 실행하면(실행 커서가 함수 안에 있으면), 해당 함수는 호출 스택의 가장 상단에 위치함. 함수의 실행이 끝날 때(리턴 값을 돌려줄 때), 스택은 해당 함수를 호출 스택에서 제거.

 

 

런타임

 많은 자바스크립트 개발자들이 setTimeout 과 같은 브라우저 내장 API를 사용한다. 하지만, 이 API를 자바스크립트 엔진에서 제공하지는 않는다.

그럼 얘네들은 대체 어디서 올까? 사실 현실은 좀 더 복잡하다.

위 그림처럼, 자바스크립트 엔진 이외에도 자바스크립트 동작에 관여하는 다른 요소들이 많다.

우측 상단의 DOM, Ajax, setTimeout 과같이 브라우저에서 제공하는 API 들을 Web API라고 한다.

그리고 그 아래쪽에 이벤트 루프와 콜백 큐도 보인다.

  • Web API: 웹 브라우저에서 제공하는 API로 AJAX나 Timeout등의 비동기 작업을 실행
  • Task Queue: Callback Queue라고도 하며 Web API에서 넘겨받은 Callback함수를 저장
  • Event Loop: Call Stack이 비어있다면 Task Queue의 작업을 Call Stack으로 옮김

 

동작하는 방식을 예시를 통해 조금 더 자세히 알아보자.

function multiply(x, y) {
    return x * y;
}
function printSquare(x) {
    var s = multiply(x, x);
    console.log(s);
}
printSquare(5);

처음 엔진이 이 코드를 실행하는 시점에는 호출 스택이 비어있다. 하지만 코드가 실행되면서 호출 스택은 아래와 같이 변한다.

호출 스택의 각 단계를 스택 프레임(Stack Frame)이라고 한다.

Stack Overflow

 가끔 함수를 재귀적으로 여러 번 부르다가 무한 루프에 빠지게 된다. 크롬 브라우저는 16000 프레임의 제한된 스택을 가지고 있는데, 이 허용치를 넘어서게 되면 Max Stack Error Reached라는 상태가 되고 실행 중이던 것을 날려버리게 된다. 이러한 날려버리는 것을 스택 날려버리기(Blowing the stack)이라 한다.

function foo() {
    foo();
}
foo();

엔진에서 이 코드를 실행할 때, foo()에 의해서 foo 함수가 호출된다. 그런데 여기서 foo 함수가 반복적으로 자신을 다시 호출하는 재귀 호출을 수행한다. 그러면 매번 실행할 때마다 호출 스택에 foo() 가 쌓인다. 아래처럼.

Max Stack Error Reached

 

 

 

비동기 콜백(Asynchronous callbacks)

 자바스크립트는 싱글 쓰레드인데. 그럼 하나의 함수 처리가 엄청 느려서 다른 함수 실행에 지장을 줄 때는 어떻게 해야 할까? 

 예를 들어, 브라우저에서 복잡한 이미지 처리를 한다고 생각해보자. JS의 단일 쓰레드, 단일 호출 스택 특성상 이미지 처리 작업 스택을 차지하고 있으면 자바스크립트는 후속 작업들을 처리할 수 없다. 또한 브라우저가 호출 스택에서 많은 작업을 처리하기 시작하면 꽤 오랜 시간 동안 응답을 멈출 수 있다. 이때 가장 쉬운 해결책이 비동기 콜백의 사용임.

 즉, 코드 일부를 실행하고 나중에 실행될 콜백(함수)를 제공한다. 비동기 콜백은 즉시가 아닌, 특수한 시점에 실행되므로 console.log와 같은 동기 함수와는 다르게 스택 안에 바로 push 될 필요가 없다. 그런데 스택이 아니라면 이 콜백 함수들은 누가 관리해...?

 

이벤트 큐(Event Queue)와 비동기 콜백(Asynchronous callbacks)의 처리 과정

 위에서 보았듯 자바스크립트 런타임은 이벤트 큐(Event Queue)를 가지고 있다. 이는 처리할 메시지 목록과 실행할 콜백 함수 들의 리스트다.

 

 

 비동기는 그럼 어떻게 처리될까? 우선 버튼 클릭과 같은 이벤트가 발생하면 DOM 이벤트, http 요청, setTimeout 등과 같은 비동기 함수는 C++로 구현된 web API를 호출하며, web API는 콜백 함수를 이벤트 큐(콜백 큐)에 밀어 넣는다. 그럼 이벤트 큐는 대기하다가 스택이 텅 비는 시점에 이벤트 루프를 돌려 스택에 넣는다. 이벤트 루프의 기본 역할은 큐와 스택, 두 부분을 지켜보다가 스택이 비는 시점에 콜백을 실행시켜 주는 것. 각 메시지와 콜백은 다른 메시지가 처리되기 전에 완전히 처리된다.

 

 웹 브라우저에서는 이벤트가 발생할 때마다 메시지가 추가되고 이벤트 리스너가 첨부된다. 따라서 리스너가 없으면 이벤트가 손실된다. 콜백 함수의 호출은 호출 스택의 초기 프레임으로 사용되며, 자바스크립트가 싱글 스레드이므로 스택에 대한 모든 호출이 반환될 때까지 메세지 폴링(polling) 및 처리가 중지된다. 동기식 함수 호출은 이와 반대로 새 호출 프레임을 스택에 추가한다.

 

 

 

 

1) 그래서 자바스크립트가 진짜 싱글 쓰레드는 맞고? 

YEAP. 더 정확하게 말하면 자바스크립트의 메인 쓰레드인 '이벤트 루프'가 싱글 쓰레드이기 때문에 자바스크립트를 싱글 쓰레드 언어라고 부르는 것. 하지만 이벤트 루프만 독립적으로 실행되지는 않으며 웹 브라우저나 NodeJS같은 멀티 쓰레드 환경에서 실행된다.

 즉, 자바스크립트 자체는 싱글 쓰레드가 맞지만 자바스크립트 런타임은 싱글 쓰레드가 아니다.

 

2) 그래서 한번에 여러 요청 처리하는건 어떻게 한다고?

비동기 작업을 통해 처리한닷

 

 

 

REFERENCE
https://joshua1988.github.io/web-development/translation/javascript/how-js-works-inside-engine/
https://chanyeong.com/blog/post/44
https://new93helloworld.tistory.com/358
https://su1993.tistory.com/76