이벤트 루프

DY·2022년 8월 18일
0

JavaScript

목록 보기
5/12

JavaScript에서 비동기 사례들

  • DOM Element의 이벤트 핸들러
    • 마우스, 키보드 입력
  • 타이머
    • 타이머 API (4가지)
    • 애니매이션 API (requestAnimationFrame)
  • 서버에 자원 요청 및 응답
    • fetch API
    • AJAX (XHR)

JavaScript는 싱글쓰레드 언어로 알려져 있다.
그러나 싱글쓰레드로 비동기를 구현할수는 없다
그렇다면 어떻게 위와 같은 비동기 사례를 구현할 수 있을까?

javascript 는 single thread 언어인가?

  • 자바스크립트는 v8엔진을 기반으로 작동한다.

    • v8엔진은 call stackheap으로 구성되어 있으며 v8엔진과 런타임(런타임은 프로그래밍 언어가 구동되는 환경이다. 즉 어떤 프로그램이 동작할 때, 프로그램이 동작하는 장소)이 만나 이벤트 루프가 작동한다.

    • 자바스크립트는 Node 나 웹 브라우저와 같은 멀티 쓰레딩이 가능한 환경에서 실행된다. 따라서 "자바스크립트 (엔진)"은 싱글 스레드이지만, "자바스크립트를 실행하는 런타임" 은 완벽한 싱글 스레드가 아니라고 할 수 있다.

  • node는 javascript를 구동하는 하나의 프로그램이고 멀티쓰레드를 지원하는 libuv 라는 라이브러리를 사용한다. (IOCP, epoll, select를 이용하는것으로 보아 멀티 플렉싱을 사용하는듯 함. (쓰레드 풀))

  • 결론적으로 node는 이벤트 루프역할을 하는 메인 쓰레드와 Thread pool 이 돌아가는 형태이다.

  • node도 그렇고 javaScript엔진도 c++ 기반이니 멀티쓰레드를 지원하는 언어이다. 다만 싱글쓰레드로 돌아가도록 구현이 되어있고 런타임환경이 멀티쓰레드를 지원하므로 싱글쓰레드이지만 비동기프로그래밍으로 작성이 가능하다. 물론 지원하는 런타임 환경에서 구동해야 한다.

Task queue, Microtask queue, Callback queue

  • 자바스크립트에는 두가지 queue가 존재한다.
  • 스크립트실행, 이벤트 핸들러, 콜백함수 등 Task가 담기는 공간이다.
  • Task가 콜백함수라면 그 종류에 따라 두가지중 한개의 queue에 담긴다.
  • Task queue, Microtask queue는 Callback queue에 속한다.
Task queueMicortask queue
setTimeout()Promise
setInterval()MutationObserver
UI 렌더링
requestAnimaionFrame()

Event loop

  • 자바스크립트 엔진과 그 실행 환경을 상호 연동시켜주는 장치
  • Web api, call Stack에 있는 콜백함수들을 콜백큐에 전달하고 두가지 큐를 감시하고 있다가 call stack이 비면 콜백함수(task)를 꺼내와서 Call Stack에 전달한다.(이러한 반복적인 행동을 틱(tick) 이라 부른다.)
    • 이때는 Microtask queue가 우선순위를 가지고 이 큐에있는 함수들이 전부실행되고나서 Task queue의 함수들이 실행된다.
    • UI렌더링은 Task queue이므로 Microtask queue에 task들이 많으면 렌더링이 지연될수 있다.

동작방식

  • Web APIs : 브라우저에서 자체 지원하는 api. 멀티쓰레드 환경이므로 실질적인 비동기 실행이 이루어 지는 곳
  • Callback queue : 콜백 함수들이 대기하는 곳(FIFO 선입선출)
  • 아래의 코드의 동작 흐름
    console.log("시작");
    setTimeout(function(){
        console.log("3초후 실행");
    }, 3000);
    console.log("끝")
  1. 전역 컨텍스트 main() 함수가 Call Stack에 쌓이고 console.log(“시작”) 이 Call Stack에 쌓인다. “시작”이 콘솔에 찍힌다.
  2. console.log(“시작”) 이 리턴되며 Call Stack에서 제거된다.
  3. setTimeout함수가 실행되면서 Call Stack에 setTimeout함수가 들어간다.
  4. setTimeout함수는 자바스크립트 엔진이 처리하지않고 Web API가 처리하므로 Callback함수를 전달하고, setTimeout작업을 요청한다. (비동기 함수는 런타임 환경에서 구동하도록 전달해준다.)
  5. Call Stack에서는 setTimeout작업이 제거된다.
  6. console.log(“끝”) 이 호출되어 Call Stack에 쌓인다. “끝”이 콘솔에 찍힌다.
  7. console.log(“끝”) 이 리턴되며 Call Stack에서 제거된다.
  8. main() 함수가 리턴되며 Call Stack에서 제거된다.
  9. Web API는 setTimeout 작업을 실행한다. 3초를 센 후 Callback Queue의 Task QueueCallback 함수를 보낸다.
  10. Event Loop는 Call Stack을 참조하고 있다가 Call Stack이 비어있으면 Callback Queue의 Task Queue에서 함수를 하나씩 꺼내 Call Stack에 넣고 실행한다.
  11. console.log(“3초후 실행”) 이 호출되고 Call Stack에 쌓인다. “3초후 실행”이 콘솔에 찍힌다.
  12. console.log(“3초후 실행”) 이 리턴되고 Call Stack에서 제거된다. Event Loop는 Callback Queue에 콜백 함수가 들어올 때까지 계속 대기한다.

동작방식의 예시

setTimeout(()=>{console.log("4")})
setTimeout(()=>{console.log("5")})
console.log("1")
console.log("2")

결과는 아래와 같다.

4
5
1
2
console.log('콜 스택!');
setTimeout(() => console.log('태스크 큐!'), 0);
Promise.resolve().then(() => console.log('마이크로태스크 큐!'));

결과는 아래와 같다

콜 스택!
마이크로태스크 큐!
태스크 큐!

예시 2

console.log('script start'); 

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

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

console.log('script end');
script start
script end
promise1
promise2
setTimeout

정리

V8 엔진에서 코드가 실행되면, Call Stack에 쌓인다.
Stack의 선입후출의 룰에 따라 제일 마지막에 들어온 함수가 먼저 실행되며,
Stack에 쌓여진 함수가 모두 실행된다.
비동기함수가 실행된다면, Web API가 호출된다.
Web API는 비동기함수의 콜백함수를 Callback Queue에 밀어넣는다.
Event Loop는 Call Stack이 빈 상태가 되면
Callback Queue에 있는 첫번째 콜백을 Call Stack으로 이동시킨다.
(이러한 반복적인 행동을 틱(tick)이라 한다.)


필립 로버츠의 event loop관련 설명
https://www.youtube.com/watch?v=8aGhZQkoFbQ

https://velog.io/@thms200/Event-Loop-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84

profile
프론트엔드 개발자가 되기 위해 공부중입니다. 블로그는 공부한 내용을 올리고 있습니다.

0개의 댓글