[웹 개발 기초 자바스크립트] 16. JavaScript Event Loop

Shy·2023년 8월 31일
0

NodeJS(Express&Next.js)

목록 보기
18/39

Event Loop

자바스크립트의 이벤트 루프(Event Loop)는 단일 스레드로 동작하는 언어의 특성 때문에 중요한 역할을 한다. 일반적으로 단일 스레드 언어는 한 번에 하나의 작업만 수행할 수 있다. 그러나 자바스크립트는 비동기 작업을 지원하기 위해 이벤트 루프를 사용한다. 이벤트 루프는 자바스크립트 엔진의 실행 컨텍스트를 관리하며, 콜백 큐와 콜 스택을 모니터링하여 프로그램이 계속해서 다른 작업을 수행할 수 있도록 한다.

콜 스택(Callback Stack)은 실행 중인 함수의 스택이다. 새로운 함수가 호출되면 그 함수는 콜 스택의 맨 위에 추가된다. 함수의 실행이 완료되면 해당 함수는 콜 스택에서 제거된다.

콜백 큐(Callback Queue)는 나중에 실행될 함수들, 즉 "콜백 함수"를 대기시키는 큐이다. 이러한 콜백 함수는 대개 이벤트 리스너나 타이머(setTimeout, setInterval 등)에 의해 발생한다.

이벤트 루프는 기본적으로 다음과 같이 작동한다.

  1. 콜 스택이 비어 있는지 확인한다.
  2. 콜 스택이 비어 있다면, 콜백 큐에 대기 중인 함수가 있는지 확인한다.
  3. 대기 중인 함수가 있다면 그 함수를 콜 스택으로 옮겨 실행한다.
  4. 이 과정을 반복한다.

이러한 이벤트 루프의 메커니즘 덕분에 자바스크립트는 단일 스레드임에도 불구하고 비동기적인 작업을 효율적으로 처리할 수 있다.

동기와 비동기

동기(Synchronous)와 비동기(Asynchronous)는 프로그래밍에서 작업의 실행 방식을 나타낸다. 두 용어는 작업이 어떻게 스케줄되고 실행되는지에 대한 본질적인 차이를 묘사한다.

동기(Synchronous)

  • 동기 방식에서는 하나의 작업이 완료될 때까지 다음 작업은 대기해야 한다.
  • 예를 들어, 파일을 읽거나 데이터베이스 쿼리를 실행하는 작업이 있다면, 해당 작업이 완료되고 결과가 반환될 때까지 프로그램은 그 다음 작업을 수행하지 않는다.
  • 동기 방식은 코드의 실행 흐름을 예측하기 쉽게 만들지만, 작업이 오래 걸리는 경우에 애플리케이션 전체가 블로킹되어 대기하는 문제가 발생할 수 있다.

비동기(Asynchronous)

  • 비동기 방식에서는 특정 작업이 완료되는 것을 기다리지 않고 다음 작업을 바로 실행한다.
  • 예를 들어, 네트워크 요청이나 파일 읽기와 같은 작업을 수행할 때, 이 작업들이 끝나길 기다리지 않고 다른 작업을 계속할 수 있다.
  • 작업이 완료되면 콜백 함수를 통해 결과를 처리하거나, Promise나 async/await와 같은 기술을 사용하여 결과를 핸들링한다.
  • 비동기 방식은 애플리케이션의 효율성을 높이지만, 코드의 복잡도가 증가할 수 있으며 실행 흐름을 추적하기 어렵게 만들 수 있다.

JavaScript는 이벤트 루프와 콜백, Promise, async/await 등의 메커니즘을 통해 비동기 프로그래밍을 지원한다. 이러한 기능 덕분에 JavaScript는 I/O 작업, 네트워크 요청, 타이머 등을 효율적으로 처리할 수 있다.

자바스크립트는 동기 언어이다!

예제

console.log('1'); // 동기

setTimeout(() => {
  console.log('2');
}, 3000); // 비동기

console.log('3') // 동기

자바스크립트는 비동기처럼 사용할 수 있다.
비동기 코드를 작성하기 위해서 자바스크립트 이외의 도움을 받는다!

setTimeout은 자바스크립트 부분이 아니다.
브라우저에서 사용한다면 브라우저api를 사용하는 것이며 (window object),
Node에서 사용한다면 Node api를 사용하는 것이다. (global object)

코드 분석

이 코드는 JavaScript에서 동기와 비동기 작업을 어떻게 다루는지를 보여주는 간단한 예제이다.

  1. console.log('1');는 동기적으로 실행되어 콘솔에 '1'을 출력한다.
  2. setTimeout(() => { console.log('2'); }, 3000);는 비동기 함수다. 이 함수는 콜백 함수를 3000 밀리초(3초) 후에 실행하도록 스케줄링한다. 그러나 이 함수는 즉시 반환하고 다음 코드로 넘어간다. 이는 이벤트 루프와 관련된 작업으로, JavaScript가 내부적으로 관리한다.
  3. console.log('3')는 동기적으로 실행되어 콘솔에 '3'을 출력한다.

코드의 실행 순서를 정리하면 다음과 같다.

  1. '1'이 출력된다. (동기)
  2. setTimeout 함수가 호출되어 3초 후에 콜백이 실행되도록 스케줄링된다. (비동기)
  3. '3'이 출력된다. (동기)
  4. 3초가 지난 후, '2'가 출력된다. (비동기 콜백 실행)

결과적으로 콘솔에는 '1', '3', '2' 순서로 출력된다. 이 예제에서는 setTimeout 함수가 비동기적으로 작동하기 때문에 '2'는 마지막에 출력되게 된다.

내부에서 어떻게 진행되는가?

자바스크립트의 실행 환경은 보통 자바스크립트 엔진, 웹 API, 이벤트 루프, 콜백 큐와 같은 여러 컴포넌트로 구성된다. 위 코드는 이러한 컴포넌트들이 어떻게 상호작용하는지를 잘 보여준다.

  1. 초기 실행: 코드가 실행되면, 자바스크립트 엔진은 코드를 위에서 아래로 순차적으로 실행한다. 먼저 console.log('1');이 호출되고, 이는 동기적으로 실행되어 '1'을 출력한다.

  2. setTimeout과 Web APIs: setTimeout 함수가 호출되면, 이 함수는 웹 API를 통해 타이머를 설정한다. 이 타이머는 3000 밀리초(3초) 동안 실행된다. 웹 API가 이 타이머를 관리하고 있으므로, 자바스크립트 엔진은 다음 코드로 넘어간다.

  3. 다음 동기 코드 실행: console.log('3');가 동기적으로 실행되어 '3'을 출력한다.

  4. 타이머 완료와 콜백 큐: 3초가 지나면, 웹 API는 타이머가 완료되었음을 이벤트 루프에 알린다. 이 때 console.log('2');를 실행할 콜백 함수가 콜백 큐에 추가된다.

  5. 이벤트 루프와 콜백 실행: 이벤트 루프는 주기적으로 콜백 큐와 자바스크립트 엔진을 확인한다. 만약 자바스크립트 엔진이 현재 어떤 작업도 하고 있지 않다면, 이벤트 루프는 콜백 큐에서 대기 중인 콜백을 꺼내와 실행한다. 이 경우에는 console.log('2');가 실행되어 '2'를 출력한다.

이렇게 여러 컴포넌트가 상호작용하면서 동기 코드와 비동기 코드가 예상대로 작동하게 된다. 이러한 방식 덕분에 자바스크립트는 단일 스레드 언어임에도 불구하고 비동기 작업을 효율적으로 처리할 수 있다.

자바스크립트 엔진

  1. 콜 스택 (Call Stack)
    콜 스택은 프로그램에서 함수의 실행을 추적하는 데 사용되는 자료 구조이다. 함수가 호출될 때 해당 함수는 콜 스택의 맨 위에 "푸시(push)"된다. 함수의 실행이 완료되면 스택에서 "팝(pop)"되어 제거된다. 콜 스택은 LIFO(Last In, First Out) 방식으로 동작한다. 이는 가장 마지막에 호출된 함수가 가장 먼저 완료되어야 함을 의미한다.

  2. 힙 (Heap)
    힙은 주로 객체와 같은 동적으로 할당되는 데이터를 저장하는 메모리 영역이다. 힙은 구조가 느슨하며, 할당과 해제가 자유로워 상대적으로 느린 접근 속도를 가질 수 있다. 자바스크립트에서 new 연산자를 사용하여 객체를 생성하면, 해당 객체는 힙에 저장된다.

  3. 이벤트 루프 (Event Loop)
    이벤트 루프는 콜 스택이 비어 있을 때 콜백 큐에 있는 함수를 콜 스택으로 이동시키는 역할을 한다. 콜 스택이 비어 있지 않으면 이벤트 루프는 대기한다. 이벤트 루프 덕분에 자바스크립트는 비동기 작업을 수행할 수 있다.

  4. 콜백 큐 (Callback Queue)
    콜백 큐는 이벤트 루프가 관리하는 또 다른 큐이다. 비동기 작업(예: 타이머, AJAX 요청 등)이 완료되면 해당 작업의 콜백 함수가 콜백 큐에 추가된다. 이벤트 루프는 콜 스택이 비어 있을 때 콜백 큐에서 함수를 가져와 콜 스택으로 옮긴다. 이 과정을 통해 함수가 실행된다.

이들 구성 요소는 자바스크립트의 실행 환경을 이루며, 서로 상호작용하면서 동기적 코드와 비동기적 코드를 모두 처리한다. 콜 스택은 코드의 실행 순서를 관리하고, 힙은 동적 데이터를 저장한다. 이벤트 루프와 콜백 큐는 비동기 작업을 관리한다. 이러한 요소들이 잘 조화되어 작동하기 때문에 자바스크립트는 유연하면서도 효율적인 프로그래밍 언어가 될 수 있다.

Call Stack 작동하는 것 살펴보기
function B() {
  setTimeout(function () {
    console.log('B-1...');
  }, 1500);
}

function A() {
  console.log('A-1...');
  B();
  console.log('A-2...');
}

A();

// A-1...
// A-2...
// B-1...

아래는 위 코드가 실행되는 과정에 따른 콜 스택, 이벤트 루프, 콜백 큐의 상태 변화이다.

  1. 초기 상태
    콜 스택: [Empty]
    이벤트 루프: 대기
    콜백 큐: [Empty]

  2. A() 함수 호출
    콜 스택: [A]
    이벤트 루프: 대기
    콜백 큐: [Empty]

  3. console.log('A-1...') 실행
    콜 스택: [A]
    이벤트 루프: 대기
    콜백 큐: [Empty]
    출력: "A-1..."

  4. B() 함수 호출
    콜 스택: [A, B]
    이벤트 루프: 대기
    콜백 큐: [Empty]

  5. setTimeout 호출
    콜 스택: [A, B]
    이벤트 루프: 대기
    콜백 큐: [Empty]
    Web API: setTimeout 시작 (1500ms 대기)

  6. B() 종료
    콜 스택: [A]
    이벤트 루프: 대기
    콜백 큐: [Empty]

  7. console.log('A-2...') 실행
    콜 스택: [A]
    이벤트 루프: 대기
    콜백 큐: [Empty]
    출력: "A-2..."

  8. A() 종료
    콜 스택: [Empty]
    이벤트 루프: 대기
    콜백 큐: [Empty]

  9. 1500ms 후, setTimeout 콜백 추가
    콜 스택: [Empty]
    이벤트 루프: 대기
    콜백 큐: [setTimeout 콜백]

  10. 이벤트 루프는 콜 스택이 비어 있음을 확인하고, 콜백 큐의 setTimeout 콜백을 콜 스택으로 이동
    콜 스택: [setTimeout 콜백]
    이벤트 루프: 대기
    콜백 큐: [Empty]

  11. console.log('B-1...') 실행
    콜 스택: [setTimeout 콜백]
    이벤트 루프: 대기
    콜백 큐: [Empty]
    출력: "B-1..."

  12. setTimeout 콜백 종료
    콜 스택: [Empty]
    이벤트 루프: 대기
    콜백 큐: [Empty]

이렇게 콜 스택, 이벤트 루프, 그리고 콜백 큐가 상호 작용하여 코드가 실행된다.

시각화 사이트
사이트

profile
초보개발자. 백엔드 지망. 2024년 9월 취업 예정

0개의 댓글