0. 누군가 Event Loop에 대해 설명해주세요, 라고 한다면?
이벤트 루프(Event Loop)는 자바스크립트의 비동기 동작을 가능하게 하는 런타임 메커니즘입니다. 자바스크립트는 기본적으로 단일 스레드 언어이며, 이는 한 번에 하나의 작업만 처리할 수 있다는 것을 의미합니다. 이벤트 루프는 자바스크립트 엔진 밖에 존재하는 브라우저 내장 기능 중 하나인데요, 이벤트 루프는 이러한 단일 스레드 환경에서도 비동기 이벤트(예: 사용자 입력, 네트워크 요청, 타이머 등)를 효율적으로 처리할 수 있도록 설계되었습니다.
일단 자바스크립트 코드 내에서 비동기 작업을 만나게 되면, 자바스크립트 엔진은 해당 작업을 자바스크립트 엔진 외부의 Web API에 위임합니다. 지정된 시간이 지나거나 이벤트가 발생하면, Web API는 콜백 함수를 Task Queue로 옮깁니다. 그러나 이 콜백은 즉시 실행되지 않고, 실행을 위해 대기 상태에 놓입니다. 이벤트 루프는 콜 스택이 비어 있는지 주기적으로 확인합니다. 즉, 자바스크립트 엔진이 현재 실행 중인 모든 코드를 완료했는지 확인합니다. 콜 스택이 비어 있고, Task Queue에 대기 중인 콜백 함수가 있으면, 이벤트 루프는 Task Queue에서 콜백 함수를 콜 스택으로 이동시킵니다. 그런 다음, 자바스크립트 엔진은 콜 스택에 있는 콜백 함수를 실행합니다.
여기서 Event Loop가 하는 일은 [1] Call Stack의 상태를 감시하고 있다가, [2] Call Stack 비어있으면 Task Queue에 대기중인 함수가 있나 확인하고, [3] 대기중인 Task가 존재한다면, Task Queue에 대기중인 첫번째 함수를 Call Stack에 push하는 것 이며, 이러한 과정을 tick이라고도 합니다.
이 과정을 통해 자바스크립트는 단일 스레드임에도 불구하고 비동기 콜백 함수를 지원하며, 사용자 경험을 방해하지 않으면서 여러 작업을 동시에 처리할 수 있습니다.
1. Event Loop가 필요하게 된 맥락
이벤트 루프는 자바스크립트가 단일 스레드(single-threaded) 환경에서 비동기(Asynchronous) 동작을 처리할 수 있게 하는 메커니즘입니다. 자바스크립트는 기본적으로 단일 스레드 언어인데, 이는 한 번에 하나의 작업만 처리할 수 있다는 것을 의미합니다. 하지만 브라우저가 동작하는 것을 살펴보면 많은 태스크가 동시에 처리되는 것처럼 느껴집니다. 예를 들어, HTML 요소가 애니메이션 효과를 통해 움직이면서 이벤트를 처리하기도 하고, HTTP 요청을 통해 서버로부터 데이터를 가지고 오면서 렌더링하기도 합니다. 이를 가능하게 하는 것이 바로 Web API, Callback Queue, Event Loop 입니다. 이 덕분에 마치 멀티 스레드로 작업하는 것 처럼 보여집니다.
자바스크립트는 스레드가 하나인데 어떻게 동시에 하고 있었지?동시성을 어떻게 지원하는 것일까?에 대한 해답을 이벤트 루프에서 찾아볼 수 있습니다.
2. Event Loop가 하는게 그래서 뭔데?
이벤트 루프(Event Loop)는 단일 스레드 환경인 자바스크립트의 비동기 동작을 가능하게 하는 런타임 메커니즘입니다.
전체적인 동작은 webApi 동작의 결과를 콜백 테스크 큐에 넣고 이벤트 루프를 통해 현재 상태에 따라 테스크 큐의 콜백을 콜스택에 push하여 실행시키는 것입니다. 여기서 이벤트 루프의 역할은 Call Stack과 Task Queue를 주시하고 있다가 Call Stack이 전부 비워지면 Task Queue에서 Callback 함수를 꺼내와 Call Stack에 push 시키는 것입니다. (이러한 반복적인 행동을 tick이라 부릅니다.)
위 그림에서 확인할 수 있듯 이벤트 루프는 브라우저에 내장되어 있는 기능 중 하나이며, 자바스크립트 엔진 내의 개념은 아닙니다. 콜 스택과 힙으로 구성되어 있는 자바스크립트 엔진은 단순히 콜 스택을 통해 요청된 Task를 순차적으로 실행할 뿐입니다. 예를 들어 비동기 방식으로 동작하는 setTimeout의 콜백 함수의 평가와 실행은 자바스크립트 엔진이 담당하지만, 호출 스케줄링을 위한 타이머 설정과 콜백 함수의 등록은 브라우저 또는 Node.js가 담당한다고 볼 수 있습니다.
(참고로 setTimeout 같은 것들은 브라우저에서 제공하는 Web API에 속하는데, 브라우저가 아닌 Node.js 환경에서는 이 기능들을 따로 에뮬레이션하여 제공합니다. 또 이와 같이 타이머 함수는 설정한 시간 후 콜백을 바로 실행하는게 아니라 큐로 메시지를 보내는 식으로 동작하기 때문에 실행 시간이 정확하게 보장되지 않습니다.)
즉, 자바스크립트 엔진은 코드의 동기적 부분을 먼저 처리하고, 비동기 작업은 Web API를 통해 관리됩니다. 이벤트 루프는 콜 스택이 비어 있을 때만 Task Queue에서 콜백 함수를 콜 스택으로 이동시켜 실행하므로, 비동기 작업은 동기적 코드 실행이 완료된 후에 처리됩니다. 이 메커니즘은 자바스크립트가 단일 스레드 환경에서도 비동기 이벤트를 효과적으로 처리할 수 있게 해줍니다.
정리
-> Event Loop가 하는 일은
[1] Call Stack 감시(비어있나 안 비어있나)하다가
[2] Call Stack 비어있으면 Task Queue에 대기중인 함수가 있나 확인하고
[3] 대기중인 Task가 존재한다면, Task Queue에 대기중인 첫번째 함수를 Call Stack에 push
⚙️ webAPI?
: Web API는 브라우저에서 제공하는 API로 DOM, Ajax, TimeOut 등이 있습니다.
: CallStack에서 실행된 비동기 함수는 Web API를 호출하고, Web API는 콜백 함수를 Task Queue에 넣습니다.
3. Task Queue?
Task Queue는 setTimeout이나 setInterval과 같은 비동기 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 보관되는 영역입니다.
Task는 MacroTask와MicroTask 이렇게 두 종류로 나뉘는데요,
표에서 볼 수 있듯이 함수마다 다른 테스크에 할당이 되게 되는데 우리가 자주 쓰는 것 중 하나인 Promise 및 관련 후속처리 메소드의 콜백함수는 MicroTask에 포함되어 있습니다. Promise가 수행되고 then, catch, finally 등이 호출되면 각 함수에 넘겨진 콜백 함수가 Micro Task에 등록이 됩니다. 그 외의 비동기 함수① setTimeout/ setInterval ② HTTP 요청 ③ 이벤트 핸들러 의 콜백 함수나 이벤트 핸들러는 Macro Task에 등록됩니다.
콜백 함수나 이벤트 핸들러를 일시 저장한다는 점에서는 둘이 동일하지만, Micro Task Queue가 Macro Task Queue 보다 우선순위가 높습니다 (우선순위 큐를 생각하시면 됩니다). 따라서 Micro Task Queue가 비어있는 상태여야만 Macro Task Queue (=Task Queue)의 콜백함수를 Call Stack에 푸쉬할 수 있습니다. (cf. UI 렌더링이 태스크 큐에 속하기 때문에 마이크로태스크 큐의 태스크가 많으면 렌더링이 지연될 수 있습니다.)
동작과정
- Call Stack이 비어있으면 Micro Task Queue에 있던 task들이 하나씩 Call Stack에 추가되고 실행된다.
- Call Stack과 Micro Task Queue 모두 비어있게 되면 Macro Task Queue를 확인하고 남아있는 작업을 수행한다.
⚙️ 이 맥락에서의 Task?
: 콜백함수 or 이벤트 핸들러 등의 함수
4. 그럼 자바스크립트에는 동시성이 없는거 아니야?
사실 메인 스레드와 이벤트 루프에 한해서는 사실상 동시성이 없다고 할 수 있습니다. 다만 비동기적으로 특정 코드의 실행을 뒤로 미루기만 할 뿐이죠. 이 미뤄진 코드들은 지금 하고 있는 작업을 마치고 곧바로, 혹은 특정 이벤트가 발생하면 실행됩니다.
But...! 이렇게 뭔가 모자라 보이는 자바스크립트의 동시성 지원은 특정 작업에 관해서는 얘기가 달라집니다.
Non-blocking I/O
브라우저에서의 경우 http 요청 등을 event loop가 직접 수행하는 대신 브라우저가 백그라운드에서 실행합니다. Node.js의 event loop는 내부적으로 모든 I/O 요청을 비동기 I/O를 지원하는 C++ 라이브러리인 libuv에게 넘깁니다.
I/O 처리를 할 때, blocking system이라면 I/O를 다른 스레드에서 처리하든 말든 결과가 나올 때 까지 기다립니다. 하지만 JS는 non-blocking I/O system을 가졌기 때문에, 다른 스레드나 OS에 요청을 맡겨놓고 기다리지 않고 나머지 코드를 계속 실행합니다. 그렇게 나머지 코드를 실행하는 동안 백그라운드에서는 스레드나 OS에 전달한 요청이 처리되기 때문에 동시성이 구현됩니다.
참고한 자료
자바스크립트 동작 원리에 대한 깊은 이해
그리고 싱글스레드 서버와 멀티스레드 서버의 차이
medium.com
추후 같이 확인해보면 좋을 자료
https://www.youtube.com/watch?v=8aGhZQkoFbQ
'프론트엔드 > 기본 프론트 지식들 모음' 카테고리의 다른 글
Critical Rendering Path (0) | 2024.03.05 |
---|---|
[WHY] 프론트엔드 개발자가 왜 네트워크 관련 지식을 알아야 할까? (1) | 2024.02.27 |
크롬에 'https://www.google.com' 이라고 주소를 적고 엔터를 치면? (1) | 2024.02.27 |
코드를 짜면서 항상 기억해야할 원칙들 (0) | 2023.09.28 |
eslint & prettier (차이 및 작동방법 등) (0) | 2022.07.06 |
댓글