자바스크립트 (JS) 이벤트루프(1)

Fizz·2022년 9월 11일
0

맥도날드 아이스크림과 버거에 주문 열에 대해 이야기 하다가 이벤트 루프이야기가 나와 정리해보려고 한다. ㅎㅎ Thinking in JS중 이벤트루프의 로빈 알고리즘은 다음글로 다루도록 하겠다.

이런 이야기를 들어본 적이 있을 것이다. JS는 싱글 스레드 기반의 언어이다. 이 싱글스레드는 이벤트 루프에 기반한다! 라는 말 , (싱글스레드와 멀티스레드, 동시성등은 나중에 CS관련으로 따로 쓰겠다. 컴퓨터 구조등의 CS지식이 일부 필요하다!)
이 글에서는 이벤트 루프에 대해 간단하게 다루어 보도록 하겠다. 일단 브라우저에서 렌더링하는 Rendering Engine과 JS Engine은 다르다는건 알고 가야한다! 그전글에서 썻든 JS는 인터프리터 언어이고 JS Engine은 JS로 쓰여진 코드를 해석하고 실행하는 인터프리터이다.ex(예시 V8)
이 인터프리터는 크게 3부분으로 나누어진다. + EvnetLoop

1. CallStack

일단 첫번째로, 함수를 호출하면, 왼쪽 그림에 보는 Stack에 쌓이게 된다. (Frame) JS는 단 하나의 callstack을 사용하기 때문에, 하나의 task가 시작되면, 완료되기까지 다른 task를 하지 못한다. 메소드가 실행되면 Frame을 push하고 끝나면 pop하는 동작원리가 된다. 이를 Run to completion이라 한다.

function foo(b) {
  let a = 10
  return a + b + 11
}

function bar(x) {
  let y = 3
  return foo(x * y)
}

const baz = bar(7) // 42를 baz에 할당

https://developer.mozilla.org/ko/docs/Web/JavaScript/EventLoop

Mdn 예제를 그대로 가져왔다. 이해를 돕기위해 설명하자면
1. bar 호출, CallStack Push(Frame > bar 인수, 지역변수 등 포함)
2. foo 호출, 첫 프레임 위로CallStack Push(Frame > bar 인수, 지역변수 등 포함)
3. foo pop
4. bar pop

이런식으로 CallStack은 움직인다.
인수와 지역변수를 저장한다는 것은 실행 콘텍스트를 통해 변수 식별자(이름) 저장, 스코프 체인 및 this 관리,코드 실행 순서 관리 등을 수행한다는 것이다.

2. Heap

Heap은 참조 데이터가 저장되는 곳이다.
즉 메모리의 할당이 일어나는 곳이다.
JS의 경우 C등 타언어와 다르게 자동적으로 일어난다 (Mark&Sweap등의 갈비지 관련 알고리즘이 있지만, 이 또한 따로 다루겠다!)
원시데이터와 참조데이터의 차이, 메모리 저장등 또한 따로 자세히 다루어보도록 하겠다. 지금은 참조데이터가 저장되는 곳이다~ 하고 넘어가 주시면 될 것 같다.

3. Task Queue (Event Queue)

오늘의 핵심 부분이다. 하지만 Task Queue 말고 Micro Task Queue가 하나 더있는데 이는 2번글에서 다루도록 하겠다.
JS 런타임 환경은 Web Api와 Task Queue를 지원해준다.
Task Queue는 JS 런타임 환경에서는 처리해야 되는 Task를 임시 저장시키는 큐가 있는데 이를 Task Queue라고 한다.
즉 우리가 비동기 호출을 위해 사용하는 Web API과 Event Loop, Task Queue는 자바스크립트 엔진 외부에 런타임 환경에 구현이 되어 있다!!
아까 말했듯 JS는 단일 Call Stack을 사용하고 Task중 다른일을 할 수 없다. (blocked) 이를 해결하기 위해 Event Loop가 나온다!

이벤트 루프는 하나의 기능만 한다. Call Stack 과 Task Queue를 감시하면서, 만약 Call Stack이 비어있다면 이벤트 루프는 큐에서 첫 번째 Task를 Call Stack에 넣고 해당 Task가 수행된다. (이러한 동작을 tick이라 한다)

정리하자면 task queue는 message를 기다리고 message가 들어오면 task queue에 추가한다. (MDN 예제)

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

그리고 이벤트 루프는 가장 오래된 메시지부터 시작해서 메시지를 처리한다. 메시지를 처리한다는 것은 함수를 실행해서 Call Stack에 올린다는 뜻이다. 즉 tick을 반복한다.

while (eventLoop.waitForTask()) {
  const taskQueue = eventLoop.selectTaskQueue();
  if (taskQueue.hasNextTask()) {
    taskQueue.processNextTask();
  }
}

그런데 이벤트 루프가 다발적으로 발생한 메시지들을 큐에 쌓고 실행을 해주면서 동시성을 확보하는 것이지 실제로 동시에 동작이 수행되는 것은 아닙니다. 이는 멀티스레드와 싱글스레드를 설명하면서 자세히 설명하겠다!

실제 실행 자체는 호출 스택에 올라가서 수행이 되므로 Run-to-completion 으로 동작한다!

2편에서는 Task Queue와 Micro Task Queue에 대해 자세히 설명하도록 하겠다
3편은 로빈 알고리즘을 할 생각이다!

profile
성장하고싶은 개발자

0개의 댓글