자바스크립트의 이벤트 루프에 대하여

Marco·2021년 11월 15일
1

Javascript TIL

목록 보기
9/12

유튜브 영상링크: 어쨌든 이벤트 루프는 무엇입니까? | Philip Roberts | JSConf EU

JSConf 채널에서 Philip Roberts의 영상을 보고 요약한 내용이다.

자바스크립트가 실제로 어떻게 동작하나?
싱글 스레드 환경이라고 하나, 콜백은 실제로 어떻게 작동할까?

자바스크립트는 싱글 스레드, 논 블로킹, 비동기, 동적(single threaded, non-blocking, asynchronous, concurrent) 언어이다.

크롬에 내장된 자바스크립트 런타임인 V8 엔진은 아래와 같이 구성됐다.
<자바스크립트 런타임을 단순화한 이미지>

Single Thread

자바스크립트는 싱글 스레드 언어로서, 싱글 스레드 런타임을 가지고 있는데, 이는 결국 한 번에 하나의 싱글 콜 스택만을 가지고 있다는 말이다.
싱글 스레드의 의미는 하나의 프로그램은 동시에 하나의 코드만 실행할 수 있다는 것이다.

Single Call Stack

콜스택은 데이터 구조로서, 실행되는 순서를 기억하고 있다. 함수를 실행하려면, 우리는 스택에 해당하는 함수를 집어넣게 되는데, 함수에서 리턴이 일어나면 스택의 가장 위쪽에서 해당 함수를 꺼내게 된다. 이게 콜스택이 하는 일의 전부다.

Blocking

블로킹은 느리게 실행되는 코드다. 예를 들어, 네트워크 요청이나 이미지 프로세싱은 느리다. 느린 동작이 스택에 남아있는 것을 보통 블로킹이라고 말한다.

  • 자바스크립트는 이렇게 느린 네트워크 요청을 하고는 다음 코드를 실행하기 위해 마냥 끝날 때까지 기다린다.
  • 문제는 브라우저가 모든 리퀘스트가 완료될 때까지 멈춰있다는 것이다.
  • 즉, 동기적으로 실행되는 네트워크 요청이 콜 스택을 블로킹하여 브라우저가 다른 일들을 하는 것을 막고 있다.이 때문에 렌더링이나 다른 코드를 실행하지 못하고 그냥 멈춰버린다.

Asynchronous callback

  • 위 문제를 해결하기 위해 비동기 콜백을 사용할 수 있다.
  • Nodejs나 브라우저의 코드는 대부분 비동기로 만들어졌다.
    • 비동기란, 어떤 코드를 실행하면 일단 콜백을 받고 이것을 나중에 실행한다는 말이다.

Concurrency & the Event Loop

동시성과 이벤트 루프

  • 앞에서 "자바스크립트 런타임은 한 번에 하나의 일만 할 수 있다"고 이야기 했는데, 이는 거짓말일까?
    • 자바스크립트 런타임은 한 번에 하나의 일만 하는 것이 맞다.
    • 하지만 ajaxsetTimeout 같은 것들과 다른 코드 실행을 동시에 할 수 있는 이유는 브라우저가 단순한 런타임 이상을 의미하기 때문이다.
    • 자바스크립트 런타임은 한 번에 한 가지만 할 수 있으나, 브라우저가 Web API 같은 것들을 제공한다. Web API 같은 것들은 자바스크립트에서 호출할 수 있는 스레드를 효과적으로 지원하며, 이를 통해 자바스크립트에서도 동시성이 가능해진다.
      (setTimeout은 브라우저에서 제공하는 API이며, V8 소스코드에는 존재하지 않는다.)

Simulating

영상에서 구현하는 부분 캡처 및 설명

  • (1) 스택에 main()과 setTimeout 함수가 순차적으로 쌓인다.

  • (2) 브라우저(webapis)가 타이머를 실행시키고, 이것은 setTimeout 호출 자체가 완료되었다는 의미이므로 스택에서 setTimeout 함수를 지운다.

  • (3) 그 다음 JSConfEU를 출력하고, main함수 실행이 완료되었으므로 스택에서 main 함수도 지워진다.

  • (4) 모든 WebAPI는 작동이 완료되면 콜백을 테스크 큐에 밀어넣는다. 타이머 5초가 지난 후 setTimeout함수는 작동이 완료되며, 해당 콜백을 테스크 큐에 밀어넣는다.
    • 콜백은 다른 함수가 부르는 함수라고 정의하거나, 앞으로 큐에 쌓일 비동기식 콜백이라고 묘사할 수 있다.
  • 드디어 이벤트 루프에 다달았다. 이벤트 루프란 무엇일까?
    • 이벤트 루프는 이 전체 시스템에서 아주 단순한 일을 하는 작은 파트이다.
    • 이벤트 루프의 역할은 콜 스택과 태스크 큐를 주시하다가, 스택이 비어있으면, 의 첫번째 콜백을 스택에 쌓는 것이다.

  • (5) 이벤트 루프는 스택이 비어있는 것을 보고 " 어, 내가 할 일이 있네. 자 이거 받아"하며 콜백을 스택에 넣어준다.

  • (6) 그 다음 스택에서 콜백 내부의 콘솔로그를 실행한다. 끝

이벤트 루프 작동과정 시각화한 사이트

latentflip loupe 예제 1

latentflip loupe 예제 2

latentflip loupe 예제 3

  • forEach 메서드, asyncForEach(커스텀함수) 작동과정 비교

    • forEach 메서드는 함수를 실행하기는 하고 콜백이 있기는 하지만, 비동기적으로 실행하지는 않고, 자신의 자체적 스택에서 실행한다. forEach 메서드를 실행하면 실행이 끝날 때까지 스택을 계속 차지하고 있음을 확인할 수 있다.
    • 한편 예제에서 asyncForEach라는 함수를 하나 선언해서 배열과 콜백을 받아, 각 요소에서 setTimeout을 0으로 설정함으로써 비동기적으로 실행할 수 있다.
  • 렌더링

    • 브라우저는 화면을 매 16밀리초마다, 1초에 60프레임을 repaint하는 것이 제일 빠르며 기본적이다.
    • 하지만 브라우저는 우리가 자바스크립트로 하는 무언가로 인해 제약을 받게 된다.
    • 즉, 스택에 코드가 쌓여 있으면, 렌더링을 진행하지 못한다. 렌더도 하나의 콜백처럼 행동하기 때문이다. 렌더링을 하려면 스택이 비워질 때까지 기다려야 한다.
      • 렌더와 다른 콜백의 차이점이라면 렌더는 우리가 만든 콜백에 비해 더 높은 우선순위를 가진다는 것이다. 매 16밀리초마다 큐에 렌더가 들어가고, 스택이 깨끗해진 후에야 렌더링이 재개된다.
    • 느린 동기식 루프를 실행하는 동안, 렌더는 막히게 된다. 렌더가 막히면, 사용자가 브라우저 화면의 텍스트를 선택하거나 선택해서 반응을 보거나 하는 것이 불가능하다.
    • 비동기식 루프를 실행하면, asyncForEach 커스텀 함수의 setTimeout을 큐에 쌓는 동안 스택에 콜백이 쌓인다. 그러나 콜백을 큐에 일단 넣고 나서는, 큐에서 스택으로 콜백을 넘기며, 콜백이 스택에서 상대적으로 빨리 사라지고 있다. 이를 통해 과정 중간마다 렌더를 할 수 있는 기회가 주어진다.
  • event loop를 막는다는 것은 스택에 필요없는 느린 코드를 쌓음으로써 브라우저가 할 일을 하지 못하도록 하는 것이다. 유려한 UI(Fluid UI)를 만들기 위해 이러한 점을 고려하자.

profile
블로그 이사 🚚 https://wonsss.github.io/

0개의 댓글