이벤트 루프와 테스트 큐

Brad·2021년 8월 13일
3

이벤트 루프와 테스트 큐를 알아야 하는 이유는 동기, 비동기 처리를 이해하기 위함이며, 동기, 비동기 처리를 이해하기 위해서는 자바스크립트 엔진과 브라우저가 어떻게 돌아가는 지 알아야 할 필요가 있습니다.
여기에선 간단하게 동기와 비동기 처리가 어디에서 어떻게 이뤄지며, 이를 위해 이벤트 루프와 테스트 큐가 어떻게 동작하는지 알아보겠습니다.

1. 실행 컨텍스트

기본적으로 JavaScript에서 함수가 호출되면 함수 실행 컨텍스트가 생성됩니다. 이때 생성된 함수 실행 컨텍스트는 콜 스택에 푸시되고 함수 코드가 실행됩니다. 함수 코드의 실행이 종료되면 함수 실행 컨텍스트는 콜 스택에서 팝되어 제거됩니다.
이런 식으로 자바스크립트 엔진은 단 하나의 콜 스택을 가집니다. 동시에 2개 이상의 함수를 실행할 수 없으며, 먼저 들어온 것부터 먼저 해결이 됩니다. 즉, 현재 진행되고 있는 실행 컨텍스트를 제외한 나머지 실행 컨텍스트들은 모두 대기중인 태스크입니다. 이것을 싱글 스레드 방식이라고 합니다.

2. 동기, 비동기 처리

싱글 스레드 방식은 한 번에 하나의 태스크만 실행할 수 있기 때문에, 직렬 구조의 방식이라고 생각하면 됩니다.
예를 들어, 태스크 1은 4초가 걸리고 태스크 2는 0.1초가 걸린다 하더라도, 이것이 순차적으로 진행된다면 태스크 2는 4초 뒤에 실행이 됩니다. 이러한 상황을 블로킹되었다고 합니다.
이와 같이 현재 실행 중인 태스크가 종료될 때까지 다음에 실행될 태스크가 대기하는 방식을 동기 처리라고 합니다. 동기 처리 방식은 실행 순서가 보장된다는 장점이 있지만, 앞선 태스크가 종료될 때까지 이후 태스크들이 블로킹되는 단점이 있습니다.

자바스크립트 엔진은 싱글 스레드 방식이며, 이로 인해 동기 처리가 가능합니다. 동기처리는 실행 순서를 보장한다는 장점이 있지만, 블로킹이 될 수 있다는 단점도 갖고 있습니다.

하지만 여기서 태스크 1이 비동기적인 함수라면 이야기가 달라집니다. setTimeout 함수를 예를 들어 보겠습니다. setTimeout 함수는 콜백 함수와 ms단위의 시간을 매개변수로 갖습니다. 정해진 시간이 지난 후, 콜백 함수를 호출합니다.
하지만 setTimeout이 실행되었다고 해서 종료될 때까지 (동기적으로) 기다리지 않습니다. 현재 실행 중인 태스크가 종료 되지 않은 상태라 해도 다음 태스크를 곧바로 실행합니다. 이를 비동기 처리라고 합니다.

비동기 처리 방식은 블로킹이 발생하지 않는다는 장점이 있지만, 태스크의 실행 순서가 보장되지 않습니다. 또한 비동기 함수는 콜백 패턴을 사용하는데, 이는 콜백 지옥을 발생시켜 가독성을 나쁘게 하고, 여러개의 비동기 처리를 한 번에 처리하는 데에도 한계가 있습니다.

3. 이벤트 루프와 태스크 큐

위와 같이 자바스크립트 엔진은 싱글 스레드 방식이며, 기본적으로 동기 처리로 돌아갑니다. 그럼 setTimeout은 어떻게 비동기 처리가 가능할까요?
여기에서 이벤트 루프와 태스크 큐가 등장합니다. 태스크 큐는 말 그대로 태스크를 담는 큐입니다. 먼저 들어간 콜백 함수가 먼저 나오는 FIFO 구조이며, setTimeout 같은 Web API에 의해 그 콜백 함수가 태스크 큐로 넘어가게 됩니다.
이렇게 태스크 큐로 넘어간 콜백 함수는 이벤트 루프에 의해 콜 스택으로 넘어갑니다. 이벤트 루프는 우선적으로 콜 스택에 있는 것들이 모두 처리가 되면 그 이후에 web API에 의해 기다리고 있는 태스크 큐의 콜백 함수를 콜 스택으로 넘겨와 실행하게 만듭니다. 정확히는, 계속해서 현재 실행중인 태스크가 없는지, 태스크 큐에 태스크가 있는지 반복적으로 확인하며 조건이 맞을 때 행동을 하게 됩니다.

이벤트 루프 예시

예시를 들어보겠습니다.

1 console.log(1);
2 setTimeout(()=>console.log(2),0);
3 console.log(3);
4 setTimeout(()=>console.log(4),0);
5 console.log(5);

자바스크립트 엔진은 첫번째 줄부터 차례로 함수 실행 컨텍스트를 콜 스택에 넣습니다. 여기에서 setTimeout의 콜백 함수를 넣는 것이 아닌 setTimeout 자체를 실행 컨텍스트에 넣게 되고, 콜백 함수는 Web API인 setTimeout을 통해 태스크 큐에 임시 저장하게 됩니다.
그동안 이벤트 루프는 계속해서 콜 스택이 비워져 있는지, 태스크 큐에 담겨있는 것이 있는지 확인을 하게 됩니다. 콜 스택이 일차적으로 모든 함수 컨텍스트를 종료하면서 1 3 5가 차례로 출력이 되면, 이때 이벤트 루프가 일을 하기 시작합니다. 태스크 큐에 있던 컨텍스트를 콜 스택으로 옮겨 이때 실행을 하게 됩니다.

태스크 큐는 Web API를 통해 들어온 컨텍스트들을 저장합니다.
이벤트 루프는 콜 스택과 태스크 큐를 번갈아 지켜보며 태스크 큐에 태스크가 존재하고, 콜 스택이 비워졌을 때 태스크 큐의 가장 먼저 들어온 태스크를 콜 스택으로 이동시킵니다.

4. 브라우저의 역할

실행 컨텍스트 파트에서 말씀드렸다시피, 자바스크립트 엔진은 싱글 스레드방식으로 진행됩니다. 그렇다면 이벤트루프와 태스크 큐는 어디에서 제공을 해주는걸까요? 바로 브라우저 혹은 Node.js 입니다.
Web API는 브라우저에서 제공하는 API이며, DOM API와 같은 비동기 처리를 포함합니다.setTimeout에서의 타이머 설정과 타이머가 만료하면 콜백 함수를 태스크 큐에 등록하는 처리를 브라우저에서 하게 되는 것입니다.

Reference

위 내용은 학습을 위해 작성되었고, 주관적으로 정리한 내용입니다. 부족한 부분이나 오류가 있다면 댓글로 남겨주시면 수정하도록 하겠습니다.

모던자바크립트 Deep Dive(책)
자바스크립트와 이벤트 루프
자바스크립트와 이벤트 루프(Event Loop)

profile
즐거운 개발자!

1개의 댓글

comment-user-thumbnail
2022년 9월 26일

안녕하세요, 정말 머리에 쏙쏙 들어오는 설명 잘 읽었습니다 :) 제가 잘 이해한건지 질문이 있는데요, 에를들어 web API 중에 ajax나 axios등으로 서버에서 데이터를 가져오는 작업은 콜스택에서 동기적인 코드들이 처리되는 도중, 브라우저에서 병렬적으로 처리되어 다운로드가 완료되면 태스크 큐에 차곡차곡 쌓이고 이것을 지켜보던 이벤트 루프가 콜스택에 밀어넣어 직렬적으로 작업을 처리하는 것이 맞을까요?

답글 달기