본문 바로가기
Front-End/JavaScript

[JS] 자바스크립트 Event Loop

by 연제원 2021. 6. 7.

자바스크립트에 대해 찾아보면 자바스크립트는 단일 스레드 기반의 언어라고 한다. 그런데 단일 스레드는 한번에 한가지 일만 처리할 수 있다. 그런데 평소에 코드를 작성하고 실행해보면서 서버에서 데이터를 가져오는 도중에 다른 일을 못해 멈추거나 이를 처리하는 데 멈추거나 한 기억은 없다. 즉, 순서대로 처리하는 동기적인 방식이 아니라 비동기적으로 구현이 가능했다. 어떻게 가능한 것일까?

 

우선 자바스크립트 엔진, 런타임 환경에 대해 알아보자.

자바스크립트 엔진


자바스크립트 엔진은 자바스크립트 코드를 해석하고 실행시켜주는 프로그램 혹은 인터프리터를 뜻한다. 대표적으로 그 유명한 크롬의 V8 등이 있다. 아래 사진은 자바스크립트 엔진을 간략하게 나타낸 모습이다. (출처)

 

Heap : 메모리 할당이 일어나는 곳

Call Stack : 코드 실행에 따라 호출 스택이 쌓이는 곳

비동기작업에 대해 이야기하기전에 일반적으로 자바스크립트 엔진에서 함수를 호출하고 실행하는 과정을 알아야 할 것 같다.

호출 스택과 실행 컨텍스트

우선 자바스크립트는 싱글 스레드다. 그러므로 한 번에 하나의 코드만 실행할 수 있다. 다음 예시를 보도록 하자.

분명 나는 함수 코드를 순서대로 짜고 실행한 것 같은데 결과를 보면 역으로 값이 나왔다. 이 이유는 자바스크립트 엔진이 함수를 호출할 때 Stack의 구조로 호출하기 때문이다. 이는 호출 스택과 실행 컨텍스트와 관련이 있기 때문이다. 과정과 코드를 함께 다시 보자.

 

main()은 기본적으로 호출되는 전역 컨텍스트, 크롬의 경우 (ananymous)함수

  1. first()가 호출되어 스택에 쌓임
  2. first() 함수 내의 middle()함수를 만나 실행 및 스택에 쌓음 => console.log('시작')은 대기 중
  3. middle() 함수 내의 end()함수를 만나 실행 및 스택에 쌓음 => console.log('중간')은 대기 중
  4. end() 내의 console.log('마지막')을 반환 및 종료 후 스택에서 사라짐
  5. middle() 내의 console.log('중간')을 반환 및 종료 후 스택에서 사라짐
  6. first() 내의 console.log('시작')을 반환 및 종료 후 스택에서 사라지고 종료

(추가로 start()와 middle()에서 console.log와 함수 실행 순서를 바꾸면 달라진다 ㅎㅎ)

 

위 과정을 보면 순서대로 진행이 잘 되고 있는 것 처럼 보이지만 그 말은 즉, 하나의 작업이 끝나기 전에 다른 작업을 못한다는 의미기도 하다. 만일 모든 과정을 순서대로 진행한다면 우리는 웹 서핑을 하면서 관련 데이터를 불러올 때 마우스, 키보드를 사용하는 등과 같은 행동은 하지 못할 것이다. 하지만 처음에 말했듯이 우리는 아무런 방해없이 잘 다룬다. 

 

런타임 환경


이를 런타임 환경이 가능하게 해준다. 크롬의 V8이 자바스크립트 엔진이라면 크롬은 런타임 환경 그 자체다.

 

런타임 환경은 브라우저가 제공해주고 자바스크립트 엔진을 구동해주는 역할이다. 간략한 그림으로 보자면 다음과 같다. (출처)

자바스크립트 엔진 외에 추가된 것들을 보자면 Web APIs, Callback Queue, Event Loop를 볼 수 있다.

  • Web APIs
    브라우저에서 제공하는 API들, 웹 브라우저에 내장되어 있으며 이를 사용해 유용한 작업을 할 수 있다.
    setTimeout이 Call Stack에 들어와 실행되면 Browser API인 timer를 호출한다.
  • Callback Queue
    이벤트 발생 시(setTimeout 등) 실행해야 할 callback 함수가 담기는 목록, 선입선출
  • Event Loop
    자바스크립트 엔진의 Call Stack과 런타임 환경의 Callback Queue를 감지
    Call Stack이 비어있을 경우, Callback Queue에서 함수를 꺼내 Call Stack에 추가

정리를 해보자면 자바스크립트가 비동기 작업을 할 수 있는 이유는 언어와 엔진 자체의 기능이 아닌 브라우저가 제공해주는 것이다. (NodeJS에서는 libuv 라이브러리가 제공)

 비동기 실행 과정에 대해 알아보자. 글로만 적으면 한번에 이해하기가 어려우므로 열심히 Miro를 통해 과정을 그려봤다! 

과정


이전의 함수실행과는 살짝 다르지만 동일한 순서대로 console.log()를 출력해보자. 그럼 다음과 같은 일이 벌어진다.

분명 순서대로라면

  1. console.log('start') 실행 및 출력
  2. setTimeout의 콜백함수를 3초뒤에 실행 => console.log('middle?') 출력
  3. console.log('end') 실행 및 출력 

이렇게 되어야하는데 2, 3 과정이 바뀌었다. 그 이유는 앞에서 말했던 브라우저의 런타임 환경덕분이다. 위의 사진을 다시보면 브라우저의 런타임 환경에는 Wep APIs, Callback Queue, Event Loop가 있다. 어떻게 작동을 하는지 (열심히 만든)그림으로 이해해보자!

 

1. console.log('start')가 호출되어 Call Stack에 담긴다.

2. 실행되어 'start'가 출력되고 Call Stack에서 제거된다.

3. setTimeout 함수가 호출되어 Call Stack에 담긴다.

4. setTimeout 함수가 실행되면서 브라우저가 제공하는 Web API를 호출하고 콜백함수(console('middle?'))가 이동한다.

5. setTimeout의 콜백함수인 console.log('middle?')은 3초 타이머 시작!

6. setTimeout 함수는 Call Stack에서 제거된다.

7. console.log('end')가 호출되어 Call Stack에 담긴다.

8. 실행되어 'end가' 출력되고 Call Stack에서 제거된다.

9. 3초 경과 후, setTimeout의 콜백함수인 console.log('middle?')은 Callback Queue에 담긴다. 

10. Event Loop가 Call Stack이 비어있는 것을 확인한 후, Callback Queue에 있는 것들을 순서대로(선입선출) Call Stack에 추가한다.

11. 'middle?'가 출력되고 console.log('middle?')은 Call Stack에서 제거된다.

 

위 과정을 통해 단일 스레드인 자바스크립트가 어떻게 비동기 작업을 수행하는 지 조금이나마 알 수 있었다.

 

더 공부해야 할 것


브라우저는 실제로 우리가 작성한 자바스크립트의 무언가로 인해 제약을 받는다고 한다. setTimeout의 delay를 1초로 적어도, 실제 지연시간은 정확한 1초가 아닌 약간 더 길어질 수 있다고 한다. 이 외에도 참고한 유튜브 영상에서 브라우저 렌더링에도 이벤트 루프가 영향을 미칠 수 있다고 했다. 브라우저 렌더링에 대해 자세히 알아봐야 할 것 같다!

댓글