이벤트루프를 들어가기 앞서 프로세스와 쓰레드의 차이점에 대해서 알아보겠습니다.
프로세스는 운영체제 위에서 독립적으로 메모리에서 실행되고 있는 프로그램을 말합니다. 1개의 프로세스는 최소 1개의 쓰레드를 가지며, 프로세스는 쓰레드의 정보를 담아 쓰레드 간의 공유 환경을 제공하는 컨테이너와 같다고 볼 수 있습니다.
그리고 프로세스 안에는 다음의 4가지가 있습니다.
쓰레드는 프로세스의 실행 단위이고 프로세스 안에서 업무를 배정받아 실행하는 일꾼 역할을 합니다. 그리고 쓰레드별로 수행되어야 하는 함수의 호출을 기억해야 하기 때문에 쓰레드마다 스택이 할당되어져 있습니다.
또한 프로세스 안에서 동작하는 쓰레드는 결국은 한 프로그램을 위해서 일해야 되므로 프로세스에 지정된 코드와 데이터, 힙들을 공통적으로 접근하여 사용합니다.
예를 들어 음악을 들으면서 사진을 편집할 수 있는 어플리케이션이 있다면 음악을 재생 하는 쓰레드 하나와 사진을 편집할 수 있는 쓰레드가 각각 있는 것입니다.
여기서 포인트는 쓰레드는 자신들이 일을 수행할 때 어디서부터 어디까지 일을 했고, 그 다음엔 어디로 가야 되는지, 이런 일의 흐름을 기억할 수 있는 고유의 스택이 지정되어져 있습니다. 하지만 데이터나, 코드나 힙 같은 공통적인 리소스는 프로세스에 있기 때문에 쓰레드들은 이 프로세스에 공통적으로 할당된 리소스에 동시다발적으로 접속해서 동시다발적으로 업데이트하며 서로 공유하면서 사용합니다..
한 프로세스 안에서 여러 가지 쓰레드가 동시다발적으로 일어나는 것을 말합니다.
멀티쓰레드를 가지고 있으면 동시다발적으로 일을 수행할 수 있기 때문에 정말 효율적으로 프로그래밍이 동작할 수 있습니다.
자바스크립트 언어 자체에는 멀티쓰레딩이 없습니다.
자바스크립트 언어 자체에는 멀티쓰레딩을 할 수 있는 방법은 없지만 이 자바스크립트가 동작하고 있는 브라우저 위에는 즉 브라우저라는 프로그램 안에서는 여러 가지의 쓰레드가 들어가 있습니다.
그래서 브라우저 웹 api's를 이용하면 멀티쓰레딩이 가능하고 그리고 자바스크립트가 동작하는 런타임 환경에서 즉 자바스크립트가 동작하고 있는 실행 환경에서는 다양한 방법을 이용해서 멀티쓰레딩 같은 효과를 얻을 수 있습니다.
그리고 자바스크립트가 실행되는 런타임 환경에서는 즉 자바스크립트가 실행되는 실행 환경 위에서는 멀티쓰레딩 뿐만 아니라 이벤트 루프를 이용해서 조금 더 다양한 동작을 실행할 수가 있습니다.
웹 어플리케이션이 브라우저에 올라가는 순간 자바스크립트 엔진은 우리가 작성한 소스 코드를 한 줄씩 해석하고 분석하고 실행하게 됩니다.
자바스크립트 엔진은 크게 Memory Heap과 Call Stack으로 나눠져 있습니다.
프로세스 안에는 힙과 스택이 있다고 했는데 자바스크립트 엔진에도 메모리 힙과 콜 스택등이 있습니다.
프로세스마다 스택마다 지정된 콜스택 사이즈가 있는데 재귀함수 등을 잘못사용하게 되면 지정된 콜스택 사이즈를 초과할 수 가 있습니다.
자바스크립트만으로 할 수 있는 것이 한정적이지만 브라우저가 제공하는 웹 APIs를 이용해서 다양한 일들을 할 수 있습니다. 대표적으로 브라우저의 멀티쓰레딩을 이용해서 조금 더 다양한 일들을 동시에 실행할 수 있다. 예를 들면 fetch를 이용해서 백엔드에서 데이터를 받아 온다든지 setTimeout을 이용해서 일정 기간의 시간이 지난 다음에 우리가 등록한 콜백 함수를 실행하는 등 멋진 일들을 할 수 있습니다.
웹 API의 콜백함수가 콜스택에 쌓이고 실행이 되면 웹 API는 이것을 태스크큐로 보냅니다. 여기서 이벤트 루프는 콜스택과 태스크큐를 계속 지켜보고 있다가 콜스택이 다 비워지면, 자바스크립트 엔진이 더이상 일하고 있지 않으면, 태스크큐는 먼저 들어온 웹api 콜백 함수 1개를 콜스택으로 보내고 자바스크립트 엔진은 콜스택으로 들어온 콜백 함수를 실행시키게 됩니다.
그래서 이벤트 루프는 프로세스가 동작하는 동안 계속 빙글빙글 루프를 돌면서 콜스택이 비어져 있다면 태스크 큐에 들어 있는 아이를 콜 스택으로 가져와서 자바스크립트 엔진이 수행할 수 있도록 도와줍니다.
그리고 버튼에 클릭이 한번 더 일어나면 우리가 등록한 콜백함수가 또 태스크 큐에 들어옵니다. 그리고 콜스택이 비워지길 기다렸다가 콜스택이 비워지면 이벤트루프는 콜백을 하나를 콜스택에 가지고 옵니다. 이 태스크큐에 있는 콜백을 한 번에 하나만 콜스택으로 가져옵니다.
이런식으로 이벤트루프를 통해서 자바스크립트 엔진과 웹 APIs가 동작하게 됩니다.
우리가 자바스크립트 코드에 버튼에 클릭 리스터를 등록해놓으면 브라우저에서 클릭이 되면 클릭이라는 이벤트가 발생하게 되면 웹 APIs는 이벤트 루프에 우리가 등록한 콜백함수를 태스크 큐 안에다 넣습니다.
하지만 모든 웹 APIs 가 태스크 큐를 이용하는 것은 아니고 createElement, appendChild 등 보통 대게의 경우에는 이벤트루프를 이용하지 않고 바로 즉각 실행됩니다.
지금 실행되지 않고, 지정된 시간이나 지정된 업무가 완료되면 Task Queue로 가는 경우는 지금 당장 일을 수행하지 않아도 되는, 또는 다른 이벤트를 기다리는 경우에 발생할 수 있습니다. (이런 것을 처리 하기 위해 Task Queue 가 만들어졌다)
태스크큐는 웹 APIs 에서 우리가 등록한 콜백함수를 특정한 이벤트가 발생했을 때, 즉 지정된 이벤트가 발생했을 때 태스크 큐에 보낸다.
마이크로 태스크 큐는 우리가 흔히 쓰는 프로미스에 등록된 콜백, 즉 프로미스가 다 수행이 되고 나면 그 다음에 등록한 then에 등록한 콜백함수, 그리고 mutation observer 라는 웹 API 중 하나가 있는데 거기에 등록된 콜백, 이것들이 마이크로 태스크 큐에 들어온다.
즉 프로미스를 사용하면 프로미스에 등록된 콜백이 여기에 들어오게 된다.
주기적으로 브라우저에서 우리가 요소들을 움직이거나 애니메이션을 할 때, 주기적으로 브라우저에 업데이트 해줘하는데 그때 주기적으로 화면에 업데이트 해주는 것이 render 이다
웹 APIs 중 하나인 리퀘스트 애니메이션 프레임이라는 API가 있는데 이 API를 통해서 콜백을 등록해 놓으면 다음에 브라우저가 업데이트 되기 전에 내 콜백을 실행해줘 라는 API가 있다 이제 그때 우리가 호출하는 콜백은 이 리퀘스트 애니메이션 프레임 이 큐에 쌓인다.
즉 렌더는 브라우저에서 우리가 변형한 코드가 주기적으로 업데이트 되기 위해서 주기적으로 호출되는 순서인데 그 전에 리퀘스트 애니메이션 프레임이라는 API를 부르면 그때 우리가 등록한 콜백은 이 리퀘스트 애니메이션 프레임 큐에 차곡 차곡 쌓인다.
이벤트 루프는 while(true) { // do something } 처럼 빙글 빙글 도는 루프 중에 하나이다.
다음으로 Render 로 넘어가게 된다.
먼저 리퀘스트 애니메이션 프레임을 통해서 등록된 콜백 함수를 하나씩 실행한 다음에 렌더트리를 만들고 그 트리를 이용해서 레이아웃을 계산한 다음 페인트를 통해 브라우저에 업데이트를 한 다음에 이벤트 루프는 다시 다음 순회를 시작한다.
브라우저는 60fps(16.7ms) 1초 동안 60개의 프레임을 보여주도록 노력한다. 사용자가 느끼는 자연스러운 애니메이션이나 화면을 보여주기 위함이다.
이렇게 되려면 16.7ms 동안 업데이트가 일어나야 된다는 말이다. 그래서 이벤트 루프는 엄청나게 빠른 속도로 돌고 있는데 한바퀴를 도는데 1ms도 걸리지가 않는다. 그래서 매번 1ms 마다 Render, 이 업데이트를 할 필요가 전혀 없다. 그래서 어느 정도 시간 있다가 (브라우저마다 지정된 시간이 조금 다르긴 한데) 대부분은 60fps(16.7ms) 범위 안에서 렌더를 한 번 업데이트 해주고 그 다음에 다시 다른 일을 하다가 루프를 돌다가, 어느 정도 시간이 됐다 싶으면 다시 이 렌더 트리를 업데이트 했다가 다시 몇 바퀴 돌다가 다시 나중에 업데이트 하는 방식으로 운영된다.
그 다음 Microtask Queue 로 넘어간다.
마이크로 태스크 큐는 큐 안에 들어있는 아이템들이 없을 때까지 기다렸다가 하나씩 콜스택으로 가지고 간다.
프로미스 then 콜백을 콜 스택에 넣게 되고 이게 끝나게 되면 다시 mutation observer 콜백을 넣게 된다.
포인트는! 여기에 머물러 있는 동안 마이크로 태스크 큐에 또 다른 콜백이 들어온다면 나중에 들어온 콜백도 전부다 끝날 때까지, 마이크로 태스크 큐가 텅텅 빌 때까지 계속 콜스택으로 가지고 와서 실행하게 된다.
그 다음 Task Queue 는 딱 하나의 아이템만 콜 스택으로 가져온다.
마이크로 태스크 큐에서는 아이템들이 없어질 때까지, 심지어 새로 들어오는 아이들도 전부 다 끝날 때까지 기다린 반면에 태스크 큐에서는 아이템 하나만 콜스택으로 보내놓고 콜백이 끝날때까지 기다린다
이렇게 콜 스택에 있는 콜백함수가 다 끝나고 나서야 이벤트 루프는 다시 순회를 시작하게 된다.
정리하자면
자바스크립트 엔진에는 힙과 콜 스택이 있다.
콜 스택은 함수가 실행되는 과정을 기억하기 위해서 쓰이는 자료구조이다.
그리고 자바스크립트가 동작하는 런타임 환경에서는 태스크 큐와 마이크로 태스크를 이용해서 비동기적인 처리를 하게 되는데 태스크 큐는 한 번에 하나씩만 가지고 오고 마이크로 태스크 큐는 들어 있는 콜백이 모두 다 수행할 때까지 콜 스택으로 가지고 오게 된다.
렌더는 이벤트루프가 주기적으로, 매번은 아니지만 주기적으로 브라우저에게 UI를 업데이트 하기 위해서 자주 자주 들러준다.
그리고 Request Animation Frame 콜백 큐는 브라우저 UI 업데이트가 일어나기 전에 코드가 실행 된다.