본문 바로가기

Frontend/javascript

[javascript] 성능 저하의 주범? JavaScript 이벤트 루프 이해하기

 

시간이 없으시다면, 아래 요약이 있습니다!

 

Why?

java script는 기본적으로 싱글 쓰레드로 구동된다, 즉 한번에 한가지 작업만 가능한 특징을 가지고 있다.

대표적으로 JAVA 같은 언어들은 다중 작업을 지원한다.
하지만 한번에 한가지 작업만 하는 것은 많은 한계점들을 가져온다.
연애할 때 멀티태스킹을 못하면 욕을 먹을 수도 있는것처럼...

 

javascript 싱글 쓰레드의 한계점은 인해 성능상 저하, 사용자 경험을 떨어트릴 가능성이 농후하다.

 


실제로 내부적으로 어떤 일들이 벌어지고 있고, 어떻게 극복했는지 상황을 쉽게 이해해보자.


나는 운전중이다. 에어컨 온도가 마음에 안들고, 틀어놓은 노래 소리 크기를 줄이고 싶다. 하지만 나는 한번에 한번의 작업밖에 하지 못한다면, 이미 운전을 하고 있는 일에 몰두하느라 다른 것들을 신경쓰지 못한다면 암담할 것이다.

그때, 테슬라 오토 파일럿(자율주행)이 눈에 띈다. 운전은 오토 파일럿에게 맡기고 나는 에어컨과 노래 등 다른 작업들을 처리한다. 이외에도 AI가 발전하여 자동차에게 맡길 수 있는 일들이 다양해진다.
이제 아주 쾌적한 운전이 가능해졌다.



이제 실제로 javascript에서 어떤 일들이 이루어지고 있는지 살펴보자.

 

 

콜 스택 (Call Stack): 프로그램 실행을 관리하는 곳
메모리 힙 (Memory Heap): 객체 등이 할당되는 메모리 영역
웹 API (Web APIs): 브라우저가 제공하는 기능들을 자바스크립트에서 사용할 수 있도록 하는 인터페이스

태스크 큐 (Task Queue) = 매크로 태스크 큐 (MacroTaskQueue): 웹 API 콜백과 이벤트 핸들러를 보관

마이크로태스크 큐 (Microtask Queue): 특정 종류의 콜백, 특히 프로미스 관련 콜백을 보관하는 특별한 큐

이벤트 루프 (Event Loop): 모든 구성 요소들을 연결하여 비동기 작업이 원활하게 실행되도록 조정하는 역할 (계속 Queue를 지켜봄)
간단한 핵심 정의들은 아 이렇게 로직공간들이 나누어져 있구나 하고 넘어가도 좋습니다. 이후 글에서 자세히 살펴보겠습니다.

 

이처럼 단순한 로직은 따라가기만 해도 생각하는 순서대로 처리됩니다.

 

여기서 위에서 살펴본 한계점을 건드려봅니다.

 

 

이 로직이 실행된다면 첫번째 태스트에서 걸려 꼭 실행되야하며, 빨리 실행되야 할 두번째 태스크가 실행되지 않고 Lock이 걸려있을 겁니다.

만약 빠르게 결과를 실행시켜 사용자에게 보여줘야 할 내용이 두 번째 태스크이고, 천천히 보여줘도 상관 없는 백그라운드 작업들이 첫번째 태스크라면 사용자 경험적으로도 굉장히 큰 차이를 보여줄 것입니다.

 

이러한 한계점을 극복하기 위해 Web APIs 가 등장합니다.

Web APIs는 브라우저가 지원하는 기능들이고 무수히 많은 함수들을 가지고 있습니다.

https://developer.mozilla.org/ko/docs/Web/API

 

Web API | MDN

웹 코드를 작성한다면 많은 API를 사용할 수 있습니다. 아래 목록은 웹 앱이나 웹 사이트를 만들 때 사용할 수 있는 모든 인터페이스(객체의 유형)입니다.

developer.mozilla.org



Web APIs (비동기 작업)

브라우저의 Web APIs는 방대한 기능을 가지고 javascript를 지원합니다. 예를들어

1. 사용자 기기의 센서, 카메라 등 데이터 활용

2. 렌더링 엔진
3. Network 서비스 (Fetch, 통신)

4. 각종 편의 함수

 

대표적인 setTimeOut 함수를 통해 전반적으로 어떻게 각 공간들이 연동되어 사용되는지 알아보겠습니다.

 

  1. setTimeout 함수 발견 -> Web APIs 공간으로 이동
  2. Timeout 시간 동안 내부 로직 처리 이후 Task Queue로 이동
  3. 계속 TaskQueue를 보고 있던 Event Loop 가 Task Queue에 담겨 있던 !실행! 로직을 Call Stack으로 이동
  4. 출력 완료

이처럼 비동기 작업들을 Web APIs 가 처리해주어 기존 로직에는 영향이 없으며 한계점을 극복할 수 있었다.

이쯤되면 의문점이 하나 생길 것이다. MicroTaskQueue는 무엇을 하나요?

 

MicroTaskQueue

MicroTaskQueue는 특정 콜백 함수들을 위해 사용됩니다.

  • 프로미스 핸들러 : then(), catch(), finally()
  • async 함수의 await 이후의 내부 로직

Event Loop의 최우선 과제는 MicroTaskQueue 입니다. MicroTaskQueue가 비워질 때까지 무조건 해당 Task들만 수행합니다.

 

 

이 예제에서 전반적인 로직 실행 원리를 이해할 수 있다.

  1. 동기 코드(Synchronous Code)
  2. 마이크로태스크큐(MicrotaskQueue)
  3. 태스크큐(TaskQueue)

핵심 요약

 

  • 싱글 스레드 (Single Thread) : 자바스크립트는 한 번에 하나의 작업만 처리할 수 있는 싱글 스레드 기반 언어입니다.
  • 비동기 처리와 위임 : 시간이 오래 걸리는 작업(예: setTimeout, 서버 통신)을 직접 처리하지 않고, 브라우저가 제공하는 별도 공간인 웹 API(Web API)에게 위임하여 백그라운드에서 처리하도록 한다.
  • 이벤트 루프 (Event Loop)와 큐 (Queue) 웹 API에서 처리가 끝난 작업들은 바로 실행되지 않고 태스크 큐(Task Queue)에서 기다립니다. 이벤트 루프는 이 큐를 계속 감시하다가, 메인 작업 공간(콜 스택)이 비었을 때 큐에 있던 작업을 가져와 실행한다.
  • 명확한 실행 우선순위 이벤트 루프는 정해진 우선순위에 따라 작업을 처리합니다.
    • 1순위: 현재 실행 중인 모든 동기 코드
    • 2순위: Promise의 .then/.catch 등 마이크로태스크 (Microtask) 전체
    • 3순위: setTimeout이나 클릭 이벤트 등 TaskQueue(MacroTaskQueue)  중 하나