본문 바로가기
Javascript study

[JS] 이벤트 루프

by 카누가 좋아요 2023. 9. 5.

📌 참고 사이트

[Javascript] 이벤트 루프 (Event Loop) 란? (tistory.com)

 

[Javascript] 이벤트 루프 (Event Loop) 란?

✨ JS, Event Loop, Single Thread NodeJs 의 single/multi thread 여부를 정리하다보니 Js 의 Event Loop 개념을 조금 더 확실히 알아두어야겠다는 생각이 들었다. [NodeJS] NodeJS 는 Single Thread 일까? Multi Thread 일까? ✨

haeunyah.tistory.com

 

https://whales.tistory.com/130

 

이벤트 루프와 태스크 큐 (마이크로 태스크, 매크로 태스크)

📖 이벤트 루프란 무엇인가요? 콜스텍(Call Stack)에서 이벤트가 순차적으로 진행되면 이어서 콜백큐(Callback Queue)에서 하나씩 동작을 Loop 시키는 것을 말합니다. 자바스크립트는 싱글 스레드 기반

whales.tistory.com

 

[OS] 싱글스레드, 멀티스레드의 의미 (velog.io)

 

[OS] 싱글스레드, 멀티스레드의 의미

먼저 프로세스와 스레드의 차이를 알아보았다. 프로세스: 운영체제로부터 자원을 할당받는 작업의 단위 디스크로부터 메모리에 적재되어 운영체제로부터 주소 공간, 파일, 메모리 등을 할당받

velog.io

 

이벤트 루프와 태스크 큐 (마이크로 태스크, 매크로 태스크) (velog.io)

 

이벤트 루프와 태스크 큐 (마이크로 태스크, 매크로 태스크)

자바스크립트는 싱글 스레드 기반의 언어이고, 자바스크립트 엔진은 하나의 호출 스택만을 사용한다. 이는 요청이 동기적으로 처리되어, 한 번에 한 가지 일만 처리할 수 있음을 의미한다. 만약

velog.io

 

 

 

📌 정독해보면 좋을 사이트

🔄 자바스크립트 이벤트 루프 동작 구조 & 원리 끝판왕 (tistory.com)

 

🔄 자바스크립트 이벤트 루프 동작 구조 & 원리 끝판왕

자바스크립트 비동기와 이벤트 루프 브라우저의 멀티 스레드로 작업을 동시에 Javascript는 싱글 스레드 언어라고 들어본 적이 있을 것이다. '싱글' 스레드라 한 번에 하나의 작업만 수행이 가능하

inpa.tistory.com

 

 

 

📋 JS의 작동 방식과 V8엔진 구성 요소

JS는 기본적으로 싱글 스레드 언어이다. 

 

 

❓ 싱글 스레드

하나의 프로세스 (운영체제로부터 자원을 할당받는 작업의 단위) 에서 하나의 스레드 (프로세스가 할당받은 자원을 이용하는 실행의 단위) 를 실행하는 것을 의미한다.

단일 스레드로 실행되므로 하나의 레지스터와 스택으로 표현된다.

 

➡️ 장점

1️⃣ 자원 접근에 대한 동기화를 신경쓰지 않아도 된다.

여러 개의 스레드(멀티 스레드에서)가 공유된 자원을 사용할 경우 각 스레드가 원하는 결과를 얻게 하기 위해서는 공용 자원에 대한 접근이 통제되어야 한다. 어떤 스레드가 먼저 공유 자원에 접근하고 수정할 것인지, 교착 상태가 발생하지 않는지, 여러 스레드가 동시에 자원에 접근하였을 때 데이터가 일관성을 유지하는지 등을고려해야 하기 때문에 복잡하다.

but 싱글 스레드 언어에서는 하나의 스레드만 그 자원을 이용하므로 접근, 동기화에 관련한 문제를 고려할 필요가 없어 코드를 더 간단하게 작성 가능하다.

 

2️⃣ 작업전환 작업을 요구하지 않는다.

작업 전환이란, 현재 실행 중인 프로세스 또는 스레드를 일시 중단하고 다른 프로세스 또는 스레드에게 CPU 실행 시간을 할당하는 작업을 의미한다. 

위 작업을 하는 비용을 싱글 스레드에서는 필요로 하지 않는다.

 

➡️ 단점

1️⃣ 여러 개의 CPU를 사용하지 못한다.

프로세서(CPU)를 최대한 활용하게 하기 위해서는 cluster 모듈을 사용하거나 외부에서 여러 개의 프로그램 인스턴스를 실행시키는 방법을 사용해야 한다.

싱글 스레드 언어는 코드를 자연스럽게 순차적으로 실행하는 것을 강조하므로 병렬 처리를 위해서는 추가적인 논리와 작업을 수동으로 구현해야 하는 것이다.

 

2️⃣ 멀티 스레드 언어에서는 여러 개의 작업을 여러 개의 스레드로 처리하는데, 짧은 시간 동안 여러 개의 스레드가 번갈아가면서 작업을 수행한다. 따라서 동시에 여러 작업이 처리되는 것과 같이 느껴진다.

but 싱글 스레드 언어는 여러 개의 작업을 하나의 스레드에서 처리해야 한다.

 

💡 but 오히려 여러 개의 스레드로 작업한 시간이 싱글 스레드로 작업한 시간보다 더 오래 걸릴 수도 있는데 이는 작업 전환 시간이 소요되기 때문이다. 따라서 단순히 CPU만을 사용하는 계산 작업이라면 싱글 스레드에서가 더 효율적일 수 있다.

 

JS는 이렇게 싱글 스레드 언어임에도 멀티스레드처럼 동시에 여러 태스크의 작동이 가능한데, 이는 아래에서 볼 런타임 환경(node 또는 브라우저)에서의 '이벤트 루프' 라는 시스템이 이를 가능하게 한다.

 

 

📍 V8 엔진의 구성 요소

JS는 JS 코드를 실행시켜주는 엔진을 필요로 하는데, 그 중 하나가 Chrome 브라우저에 내장된 JS 실행 엔진 V8이다. V8은 크게 두 가지 구성 요소로 나뉜다.

 

➡️ Heap

메모리 할당이 이루어지는 공간이다.

객체들은 힙 메모리에 할당되며, 크기가 동적으로 변하는 값들의 참조 값을 가지고 있다.

 

➡️ Call Stack

실행되어야 하는 작업이 쌓이는 공간으로, 싱글 스레드이기 때문에 한 번에 하나의 작업만 수행이 가능하다.

함수 호출 시 실행 컨텍스트가 생성되며, 이 실행 컨텍스트들이 콜 스택을 구성한다.

 

 

 

📋 이벤트 루프 (Event Loop)

📍 이벤트 루프란?

이벤트 루프는 런타임 환경에 내장된 여러 장치들과 JS 엔진을 활용해 비동기 처리를 하는 일종의 동작 시스템이다.

작업 시간이 오래 걸리는 코드의 경우 비동기적으로 실행하도록 설정해 둠으로써 Call Stack이 아닌 다른 영역에서 작업을 하도록 만들어 Blocking 현상을 방지할 수 있게 된다.

➡️ 브라우저에서 제공하는 Web API 비동기 함수를 병렬적으로 처리할 수 있게 한다.

➡️ Node.js에서는 비동기 IO를 지원하기 위해 libuv 라이브러리를 사용하는데, 이 libuv가 이벤트 루프를 제공한다.

 

❓ Web API

웹 브라우저에 구현된 API로, DOM event, AJAX, Timer 등이 여기에 속한다.

 

📍 이벤트 루프의 역할

태스크 큐(Task Queue)와 콜스택(Call Stack)을 꾸준히 감시하여 콜 스택이 비었을 때 태스크 큐에 있는 콜백 함수를 처리하는 역할을 한다.

웹 사이트나 앱의 코드는 메인 스레드에서 실행되는데, 이 메인 스레드는 사이트의 코드를 실행시키고, 이벤트를 받고 실행시키고, 웹 컨텐츠를 엔더링하거나 페인팅하는 역할을 하는데 이벤트 루프는 스레드 안에 있는 코드들을 스케쥴링하고 실행시키는 역할을 담당한다.

 

 

📍 태스크 큐

태스크 큐는 이벤트 큐와 잡 큐로 나뉘어진다. 웹 브라우저 엔진 내부에 위치해 있다.

 

* JS 코드 실행과 관련된 메모리 사용은 브라우저 내부에서 처리되며, 브라우저가 할당한 메모리 공간 내에서 이루어진다. 브라우저는 자체적으로 메모리 관리를 수행하며 브라우저의 메모리 사용량이 증가하면 필요한 경우 가비지 컬렉션을 실행해 더 이산 사용되지 않는 메모리를 해제하므로 개발자는 메모리 관리를 일반적으로 신경 쓸 필요는 없다.

 

➡️ 이벤트 큐 (Event Queue, 매크로 태스크 큐)

requestAnimationFrame, I/O, UI 렌더링, setTimeout, setInterval, setImmediate, XMLHttpRequest 등의 비동기 작업들이 이벤트 큐에 추가된다.

주로 비동기 이벤트와 작업을 처리한다고 생각하면 된다.

(* 매크로 태스크 큐는 이벤트 큐의 일부이다.)

 

➡️ 잡 큐 (Job Queue, 마이크로 태스크 큐) 

process.nextTick, Promises, queueMicrotask, MutationObserver, async 함수 등의 비동기 작업들이 잡 큐에 추가된다.

주로 Promise와 관련된 비동기 작업을 처리하는 데 사용된다고 생각하면 된다.

 

 

📍 이벤트 루프의 동작 방식

1️⃣

 

스크립트 실행 중 Stack에 들어온 동기 함수는 바로 실행되어 Stack을 빠져나가지만 비동기 함수는 브라우저의 Web API로 이동해 그 공간에서 작업을 진행한다. 

위의 경우 Call Stack에 첫 번째로 들어가는 bar() 함수는 console.log('First')를 하는 동기 함수이다.

두 번째로 들어가는 함수는 setTimeout(() => console.log('Second')) 비동기 함수이다. 비동기 함수인 setTimeout의 경우 Web API로 이동해 그 공간에서 작업을 진행한다. Web API 안에서 타이머가 돌아가는 것이다. 타이머 작업이 완료되는 순간 브라우저의 Task Queue에 함수가 들어가게 된다.

이 과정에서 콜 스택에 처음으로 들어온 bar() 함수밖에 남지 않았으므로 그 함수가 실행되어 First가 우선 출력된다.

이후에 console.log('Third') 동작을 하는 동기 함수 foo()가 들어오면 역시 콜 스택에 그 함수밖에 없으므로 실행되어 Third가 다음으로 출력되게 된다.

이제 콜 스택이 완전히 비게 되고, 이벤트 루프는 태스크 큐와 콜 스택을 계속 지켜보다가 콜 스택이 빈 것을 알아차리게 되면 태스크 큐에 들어온 첫 번째 요소부터 순서대로 다시 Stack에 돌려 함수가 실행되도록 한다.

따라서 'Second'가 가장 마지막에 출력되게 된다.

 

2️⃣  

 

그런데 태스크 큐에도 우선순위가 있다.

위 그림과 같이 마이크로 태스크 큐 (잡 큐)에 들어간 작업들이 모두 처리된 다음 매크로 태스크 큐 (이벤트 큐)에 들어간 작업들이 처리되게 된다.

 

 

 

✏️ 코드 실행 결과 예측해 보기

다음 코드의 실행 순서와 결과를 예측해 보자.

 

console.log('script start');     // 1

setTimeout(function() {     // 2
  console.log('setTimeout');     
}, 0);

Promise.resolve()
  .then(function () {     // 3
    console.log('promise1');     
  })
  .then(function () {     // 4
    console.log('promise2');     
  });

console.log('script end');     // 5

// 결과
// script start
// script end
// promise1
// promise2
// setTimeout

 

1. Call Stack에는 전역 실행 객체가 있고, 'Run Script' 이라는 태스크가 매크로 태스크 큐(이벤트 큐)에 들어 있다.

2. 이벤트 루프는 매크로 태스크 큐에 있는 'Run Script' 태스크를 실행한다.

3. 1에 도달하면 'script start'가 출력된다.

4. 2에 도달하면 setTimeout web api가 타이머를 실행시키고, 타이머가 종료되면 콜백 함수가 매크로 태스크 큐(이벤트 큐)에 들어간다.

5. 3에 도달하면 콜백 함수가 마이크로 태스크 큐(잡 큐)에 들어간다.

6. 5에 도달하면 'script end'가 출력된다.

7. 스크립트가 끝나고, 콜 스택이 비었으므로 이벤트 루프는 마이크로 태스크 큐에 있는 Promise 콜백 함수를 실행시켜 promise1이 출력된다.

8. 해당 then 메서드에서는 undefined를 반환하고 다음 Promise.then 메서드는 4번의 콜백 함수를 마이크로 태스크 큐에 등록한다.

9. 이벤트 루프는 다음 마이크로 태스크인 4번의 콜백 함수를 실행시켜 'promise 2'가 출력되게 된다.

10. 렌더링할 것이 있으면 브라우저는 렌더링을 한다.

11. 매크로 태스크 큐 (이벤트 큐)에 있는 setTimeout 콜백 함수를 실행시켜 'setTimeout'이 출력된다.

'Javascript study' 카테고리의 다른 글

[JS] 동기와 비동기, 블로킹과 논블로킹  (0) 2023.09.08
[JS] async와 await  (0) 2023.09.04
[JS] fetch  (0) 2023.09.03
[JS] Promise  (0) 2023.08.28
[JS] IndexedDB  (2) 2023.08.27

댓글