Event Loop??? 그게 뭔데??

killi8n·2020년 1월 17일
2

Javascript

목록 보기
1/1

EventLoop 를 알아보기 전에, JS 는 어떻게 이루어져 있는가?

  1. Call Stack: 프레임들의 스택.

  2. Task queue: 처리할 메시지의 대기열. (비동기로 호출되는 함수들(콜백 함수)이 이곳에 쌓인다.)

  3. Heap Memory: 구조화 되지 않은 메모리 공간.

즉, 모든 콜 스택들이 빠지고 나서야, Task Queue 에 쌓여있던 것들이 이벤트 루프에 의해서 콜스택이 비어있기를 기다리다가 다시 콜스택으로 들어가서 실행.

따라서 이벤트에 걸려있는 핸들러는 절대 먼저 실행 될 수 없다.

JS에서의 비동기 동작.

JS: 싱글 쓰레드로 동작한다.

그 말은 즉슨 한 함수가 끝나기 전까지 다른 함수가 진행중인 함수를 막을 수 없다는 것.

그렇담, 비동기적으로 작동하는 것은 어떻게 구현할까?

js는 이렇게 비동기를 구현한다.

while(queue.waitForMessage()){
  queue.processNextMessage();
}

queue에 메시지가 들어오기를 계속 기다린다.
그리고, queue.waitForMessage() -> true가 되는 순간(queue에 메시지가 들어왔다 는 뜻.) 바로 들어온 메시지를 실행한다.
하지만 싱글 쓰레드의 성질이 있는만큼, 기존 먼저 들어온 메시지가 있다면, 그 메시지가 끝나고 난 뒤 그 후에 들어온 메시지를 실행한다.

다음과 같은 자바스크립트 코드가 있다고 가정하자.

console.log('Hi!'); // 동기

setTimeout(() => {
    console.log('Yay!');
}, 4000); // 비동기

console.log('Bye!'); // 동기

Hi! 와 Bye!를 찍는 콘솔 로그 함수들은 모두 동기적으로 작동한다.

따라서 코드가 위에서 아래로 실행될때에, console.log('Hi!');는 제일 먼저 콜 스택에 쌓인다.
실행 되면 pop 되어 콜스택에서 사라진다.

그리고 콘솔 창에 Hi! 를 찍게 된다.

다음 코드인 setTimeout 함수가 콜스택으로 들어간다.
setTimout(cb, 4000);
콜스택에서 setTimeout 함수가 나온다.

그렇지만 아무것도 동작 하지 않고 다음 코드인 Bye!를 찍는 콘솔로그 함수가 다시 콜스택에 들어간다.
Pop 된다. Bye!가 찍힌다.

그리고 4초뒤에 Yay!가 찍힌다.

왜그럴까?

생각 같아서는 Hi! -> 4초간 기다림 -> Yay! -> Bye! 가 찍혀야 할 것 같은데 말이다.

여기서 Event Loop 가 나온다.

이벤트 루프의 역할은 콜스택을 주시 하는 것이다.

즉, 이벤트 루프에 쌓인 이벤트들은 콜스택을 계속 모니터링하면서 눈치를 보다가,
콜스택이 비었을시 이벤트 루프에 있는 이벤트들을 콜스택에 잽싸게 넣는다.

그제서야 이벤트 루프에서 아무것도 안하고 빙빙 루핑하던 이벤트가 실행된다.

위 코드로 다시 돌아가보면, 동기 함수인 콘솔로그들이 모두 콜스택에서 빠져나가고 나서야(모두 실행되고 나서야),
setTimeout의 콜백인 console.log('Yay!'); 가 이벤트 루프에 담겨있다가, 4000ms 의 대기시간을 가진 뒤 실행 되는 것이다.

그렇다면, setTimeout 의 딜레이 시간을 0으로 두면 어떨까?

console.log('Hi!'); // 동기

setTimeout(() => {
    console.log('Yay!');
}, 0); // 비동기

console.log('Bye!'); // 동기

마찬기지다. 결과는 똑같다. Hi! -> Bye! -> Yay!가 된다.

이유는 위에서 설명한 것과 같다.

0초를 딜레이 한다는것이 동기적으로 작동하게 만든다는 의미는 절대 아닌것이다.

web API

단순 자바스크립트 함수가 아닌 web API를 타는 함수들은 비동기라고 볼수있다.
위의 setTimeout 함수도 webAPI를 타는 함수이기 때문에, 제일 마지막에 실행 되었던 것이다.

Ajax 도 마찬가지이다.

console.log('Hi! again.');

fetch(.......).then(() => {
    console.log('Fetched!');
});

console.log('Bye! again.');

위 코드는 Ajax 통신의 예제이다.

자 마찬가지로, 먼저 Hi! again을 찍는 콘솔 로그함수가 콜스택에 쌓이고, 빠져나오고 실행된다.
그다음 fetch 함수가 콜스택으로 들어가는데, webAPI인 fetch를 실행함으로,fetch 함수는 XHR통신이 모두 완료될때까지 계속 일한다.
하지만 콜스택에서는 fetch 함수가 이미 빠져나간다.
따라서 Bye!again을 찍는 함수가 콜스택으로 들어갔다 나옴으로써 실행되며 Bye!again. 이 먼저 찍히고,
XHR 통신이 마무리 되었다면, fetch의 콜백인 console.log('Fetched!'); 가 task queue로 들어가게 되고 이벤트루프는 비어있는 콜스택을 발견하고 콜백함수를
콜스택으로 넣는다.

이제서야 console.log('Fetched!');가 실행 되는 것이다.

좀더 심화된 예제를 보자.

console.log('Oops!');

button.addEventListener('click', () => {
    console.log('hehe did you click me?');
});

setTimeout((() => {
    console.log('I waited 3000ms!');
}), 3000);

console.log('Yay!');

자 위 코드는 어떻게 실행될것인지 예상이 가십니까??

결과는 아래와 같습니다.

Oops!
Yay!
I waited 3000ms!

hehe did you click me? 는 버튼을 클릭할때 실행됩니다.

따라서 무조건 Oops! 와 Yay! 보다는 늦게 실행 될수밖에 없습니다.

web api이기 때문이죠, 마찬가지로 이벤트 루프의 주시하에 Task Queue에 상주하고 있다가, 동기함수들이 실행되고 나서야 콜스택으로 가서 실행됩니다.

만약 3초 안에 잽싸게 버튼을 누른다면, I waited 3000ms! 보다 먼저 찍히겠죠.

근데 저 3000ms , 즉 3초가 정확한 3초를 의미할까요?

정답은 아니다. 입니다.

왜냐면 함수를 모두 처리하기 까지의 최소 지연 시간이 3초 라는 의미기 때문이지요.

따라서 setTimeout 의 두번째인자를 0으로 두어도, 비동기적으로 작동합니다.

이유는 최소 기다림의 시간이 0ms 라는 말이지, 0ms 만에 실행되지는 못합니다. 비동기 함수이기 때문에, task queue 에서 기다렸다 실행되기 때문이죠.

async 함수들의 이점 (benefit)

자 그렇다면, 브라우저의 렌더링은 비동기일까요?
맞습니다.

웹 api 이잖아요.

애초에 브라우저가 렌더링 하여 보여주는것 자체가 비동기 event 라는 겁니다.

render queue 라고도 하는데, 다른 비동기 웹 API 보다 우선권을 갖습니다. (하지만 콜스택에 들어가는 동기 함수보다는 우선권이 작습니다.)
따라서 매 16ms 마다 큐에 렌더가 들어가고 스택이 깨끗해진 후에야 렌더링을 합니다.

그런데, 그 위에 많은 동기 이벤트(비동기가 아닙니다, 동기!)를 쌓게 되면, 브라우저 렌더링이 멈추겠죠?

왜냐면, 다른 콜스택에 들어간 동기 함수를 먼저 실행하고 있기 때문입니다. 그럴때, 브라우저는 작동이 멈춥니다.

처리하는데 시간이 많이 걸리는 작업들을 동기 함수로 구현하게 된다면 그 동기 함수를 여러번 실행 하다보면, 브라우저가 멈춥니다.
이유는 렌더 비동기 함수가 다른 시간이 많이 걸리는 동기 함수들을 콜스택에서 사라지기까지 기다리기 때문이죠.

그러나, 동기 함수가 아닌 비동기 함수로로 시간이 걸리는 작업들을 구현하게 되면, 브라우저가 멈추는 현상이 훨씬 줄어듭니다.

왜냐면, 비동기 함수들 중간중간에 렌더 함수가 끼어들수 있기 때문입니다. (16ms 마다 끼어들수 있음.)

Don't block the Event Loop!

이벤트루프를 막지 말아라! 라는 말은 결국 쓸데없이 시간이 많이드는 일들은 동기 함수로 구현하지 말고 비동기함수로 구현하라는 말입니다.
비동기 함수는 이벤트 루프를 막는 확률이 적죠.

저 어딘가에서 뒤에서 실행되다가, 실행이 완료되면 그때 콜백을 콜스택에 넣으니까요.

Web API 그리고 Javascript Engine

정말 pure 한 자바스크립트 함수들, 예를 들면 console.log() 같은 함수들은 동기적 함수입니다.

진짜 자바스크립트 그 자체 함수이기 때문이죠. 즉, 이벤트가 아니라는 겁니다.

근데 우리가 실 상용하고 있는 웹앱들은 이벤트 투성이입니다. 즉 배열을 돌리거나, 덧셈 뺄셈 과 같은 사칙연산을 하거나 이런 함수들로만 이루어져있지 않단 말이죠.

그렇다면 이런 Web API 이벤트들은 어떻게 실행될수 있는 걸까요?

정답은 바로 Javascript Engine 에 있습니다.

대표적으로 구글에서 만든 V8 엔진이 있죠. 현재 크롬에서 사용하고있는 자바스크립트 엔진입니다.

위 링크에서 자바스크립트 엔진에 대해 더 자세히 알아볼수 있습니다.

이러한 자바스크립트 엔진을 타는 이벤트들은 모두 비동기 함수입니다. browser API 를 타는 함수들 이죠.

위에서 자주 쓰인 setTimeout도 자바스크립트 엔진에서 돌아갑니다.
우리가 자주쓰는 이벤트리쓰너도 마찬가지 입니다.

이런 자바스크립트 엔진에 대해서 자세히 공부하면, 좀더 event loop를 이해하는데 도움이 되지 않을까 싶습니다.

추가적으로, 이 글에서 맞지 않는 내용이 발견 된다면 그 즉시 댓글로 보고해주세요!!!!!

profile
killi8n

0개의 댓글