Javascript/core

자바스크립트의 런타임, 엔진 그리고 이벤트 루프에 대하여

student513 2021. 3. 29. 15:32

한창 취준을 하면서, 연습삼아 여기저기 인터뷰를 보고 있다.

오늘은 화상통화를 하며 1시간 동안 js와 react 문제를 해결하는 형식의 인터뷰를 진행했다.

그리고 1번으로 나온 문제다. 

for (var i = 0; i < 10; i++) {
  setTimeout(function() {
    console.log(i);
  }, 10);
}

늘상 코딩테스트 단골문제로 나온다고 다른 블로그에서 스치듯 지나간 문제이다.

언뜻 보기에 별 문제없는 코드이고 정상적으로 0부터 9까지 출력될 것 같다.

 

그러나 실상은 다르다.

 

10이 10번 출력되었다!!

제대로 된 원리를 알지 못해 미심쩍어하면서도 0~9를 적은 본인을 탓하며, 왜 이렇게 돌아가는지 조사해보았다.


간단하게 말해보자면 setTimeout 내의 callback 함수는 모든 함수가 종료되고 난 뒤 실행된다.

이를 이해하기 위해서는 자바스크립트의 런타임과 엔진의 작동원리에 대하여 알고 있어야 한다.

 

자바스크립트는 싱글 스레드 기반의 언어로, 한 번에 한 가지 작업밖에 처리할 수 없다.

한 가지의 작업만을 수행한다면 연산시간이 오래 소요되는 함수를 만났을 때 웹 페이지가 먹통이 될 수 밖에 없을 것이다.

이러한 현상을 blocking이라 하는데, 싱글스레드의 단점을 보완하기 위해 자바스크립트 런타임은 콜백을 이용한 non-blocking 방식을 채택하고 있다.

 

자바스크립트 런타임 내의 자바스크립트 엔진은 하나의 콜 스택을 갖고 있다.

함수가 호출되면 이 콜 스택에 쌓여 하나씩 차례대로 처리된다.

크롬 런타임의 V8 엔진

콜 스택 달랑 하나만 있다면 setTimeout이 들어오게 된 후, 몇 초동안을 꼼짝없이 기다려야하겠지만, 실제로는 그렇지 않다

console.log("start")

setTimeout(()=>{
  console.log("loading...")
}, 1000)

console.log("finish")

// output: start => finish => loading...

콜 스택에서 호출된 setTimeout은 사실 자바스크립트의 내장 함수가 아닌 Web API에 속한 함수이기 때문에 외부에서 관리된다.

Web API의 함수는 콜백 큐에서 별도로 관리되는데, 이 경우 setTimeout 내의 콜백 함수가 콜백 큐에 들어가게 된다.

 

이벤트 루프라는 녀석은 콜 스택을 항상 감시하며 콜 스택이 비워지기만을 호시탐탐 기다린다. 

콜 스택이 비워지면,  콜백 큐에 쌓인 setTimeout의 콜백함수 console.log를 콜 스택에 얹어준다.

그제서야 "loading..."이 콘솔에 출력되는 것이다.

 

이 과정은 다음의 움짤로 표현할 수 있다.

출처는 아래에


다시 원래 문제로 돌아가보자

우린 콜백 함수는 다른 함수들과 달리 콜백 큐에서 별도로 관리된다는 사실을 알게 되었다.

그 콜백 큐는 콜 스택이 완전히 비워진 다음에 이벤트 루프를 통해 콜백 함수를 실행해준다.

for (var i = 0; i < 10; i++) {
  setTimeout(function() {
    console.log(i);
  }, 10);
}

이에 따르면 콜 스택에 가장 먼저 쌓이게 될 함수는 for loop이다.

for loop가 실행되며 10개의 setTimeout이 콜 스택에서 콜백 큐로 옮겨진다.

for loop가 종료되고 콜 스택이 비게 되면 이벤트루프는 콜 스택이 빈 것을 감지하고 콜백 큐의 setTimeout 함수들을 호출한다.

 

그런데 이상하다. 왜 10이 출력될까?

실행되는건 알았고 어쨌든 0~9가 출력되어야하는 것 아닐까?

왜 10이 출력되지?

여기서 클로저라는 개념을 알고 있어야 한다.

쉽게 말하자면 함수가 종료된 후에도 지역변수를 참조할 수 있다는 것이다.

 

for loop가 끝나게 되는 조건은 i가 10이 되었을 때다.

setTimeout 함수들은 이미 for loop가 종료된 이후에 실행되기 때문에 뒤늦게서야 for loop에 존재하던 지역변수 i를 참조하게 되는 것이다.

for loop가 종료될 당시의 i는 10이므로, 10을 열 번 출력하게 되는 것이다.


+ 추가 개념

setTimeout(mycallback, 1000)

setTimeout의 두 번째 인자는 함수가 호출된 후 1000ms 후에 실행이 될 것이라는 것이 아니다.

엄연히 말하자면 1000ms 이후에 큐에 추가될 것이라는 뜻이다.

이후에 콜스택이 비워지길 기다렸다 추가될 것이다.

그 말인즉, 정확히 1000ms 후에 실행됨을 보장하진 않는다는 것이다.


출처

 

Watch Out When Using SetTimeout() in For Loop #JS

When you see the above code snippet, what do you expect the console.log(i) will do? 1, 2, 3, 4, 5 respectively with a 1-second delay for…

medium.com

 

 

자바스크립트는 어떻게 작동하는가: 이벤트 루프와 비동기 프로그래밍의 부상, async/await을 이용

How JavaScript works: Event loop and the rise of Async programming + 5 ways to better coding with async/await

engineering.huiseoul.com