[JavaScript] - Event loop

배정규·2021년 6월 5일
1

javascript

목록 보기
6/7
post-thumbnail

이벤트루프를 들어가기 앞서 프로세스와 쓰레드의 차이점에 대해서 알아보겠습니다.

프로세스

프로세스는 운영체제 위에서 독립적으로 메모리에서 실행되고 있는 프로그램을 말합니다. 1개의 프로세스는 최소 1개의 쓰레드를 가지며, 프로세스는 쓰레드의 정보를 담아 쓰레드 간의 공유 환경을 제공하는 컨테이너와 같다고 볼 수 있습니다.

그리고 프로세스 안에는 다음의 4가지가 있습니다.

  • Code: 작성된 코드, 즉 프로그램을 실행하기 위한 코드(컴파일된 머신코드)
  • Stack: 프로그램이 사용하는 임시 메모리 영역으로, 함수 호출 시 생성되고 함수 실행이 끝나면 제거됩니다. 함수들이 어떤 순서로 실행되어야 하는지, 이 함수가 끝나면 어디로 다시 돌아가야 되는지에 대한 정보를 저장합니다. (지역변수, 매개변수)
  • Heap: 동적 할당을 위한 메모리 영역입니다. 오브젝트를 생성하거나 데이터를 만들 때 그 데이터들이 저장되는 공간입니다. (동적으로 할당된 변수들, 컴파일 시기에 크기를 알 수 없는 데이터)
  • Data: 전역 변수나 스태틱 변수들이 할당됩니다.

쓰레드

쓰레드는 프로세스의 실행 단위이고 프로세스 안에서 업무를 배정받아 실행하는 일꾼 역할을 합니다. 그리고 쓰레드별로 수행되어야 하는 함수의 호출을 기억해야 하기 때문에 쓰레드마다 스택이 할당되어져 있습니다.

또한 프로세스 안에서 동작하는 쓰레드는 결국은 한 프로그램을 위해서 일해야 되므로 프로세스에 지정된 코드와 데이터, 힙들을 공통적으로 접근하여 사용합니다.

예를 들어 음악을 들으면서 사진을 편집할 수 있는 어플리케이션이 있다면 음악을 재생 하는 쓰레드 하나와 사진을 편집할 수 있는 쓰레드가 각각 있는 것입니다.

여기서 포인트는 쓰레드는 자신들이 일을 수행할 때 어디서부터 어디까지 일을 했고, 그 다음엔 어디로 가야 되는지, 이런 일의 흐름을 기억할 수 있는 고유의 스택이 지정되어져 있습니다. 하지만 데이터나, 코드나 힙 같은 공통적인 리소스는 프로세스에 있기 때문에 쓰레드들은 이 프로세스에 공통적으로 할당된 리소스에 동시다발적으로 접속해서 동시다발적으로 업데이트하며 서로 공유하면서 사용합니다..

멀티쓰레딩 (Multithreading)

한 프로세스 안에서 여러 가지 쓰레드가 동시다발적으로 일어나는 것을 말합니다.

멀티쓰레드를 가지고 있으면 동시다발적으로 일을 수행할 수 있기 때문에 정말 효율적으로 프로그래밍이 동작할 수 있습니다.

자바스크립트는 싱글쓰레드 언어이다. (single threaded language)

자바스크립트 언어 자체에는 멀티쓰레딩이 없습니다.

자바스크립트 언어 자체에는 멀티쓰레딩을 할 수 있는 방법은 없지만 이 자바스크립트가 동작하고 있는 브라우저 위에는 즉 브라우저라는 프로그램 안에서는 여러 가지의 쓰레드가 들어가 있습니다.

그래서 브라우저 웹 api's를 이용하면 멀티쓰레딩이 가능하고 그리고 자바스크립트가 동작하는 런타임 환경에서 즉 자바스크립트가 동작하고 있는 실행 환경에서는 다양한 방법을 이용해서 멀티쓰레딩 같은 효과를 얻을 수 있습니다.

그리고 자바스크립트가 실행되는 런타임 환경에서는 즉 자바스크립트가 실행되는 실행 환경 위에서는 멀티쓰레딩 뿐만 아니라 이벤트 루프를 이용해서 조금 더 다양한 동작을 실행할 수가 있습니다.

자바스크립트 엔진

웹 어플리케이션이 브라우저에 올라가는 순간 자바스크립트 엔진은 우리가 작성한 소스 코드를 한 줄씩 해석하고 분석하고 실행하게 됩니다.

자바스크립트 엔진은 크게 Memory Heap과 Call Stack으로 나눠져 있습니다.

프로세스 안에는 힙과 스택이 있다고 했는데 자바스크립트 엔진에도 메모리 힙과 콜 스택등이 있습니다.

  • Memory Heap: 변수를 선언해서 오브젝트를 할당하거나 문자열이나 숫자를 할당하게 되면 그 데이터들은 전부 다 메모리 힙에 저장됩니다. 그리고 메모리힙은 구조적으로 정리된 자료 구조가 아니라 자료들이 여기저기 아무곳에서나 저장되어져 있습니다. 이렇게 할당된 데이터들이 메모리 힙에 들어가게 됩니다.
  • Call Stack: 우리가 함수를 실행하는 순서에 따라서 차곡 차곡 쌓이게 됩니다. stack이라는 자료구조와 같은 구조입니다. 콜 스택은 함수들이 호출하는 순서를 기억했다가 함수 실행이 끝나면 원래 있던 자리로 돌아가기 위해서 쓰이는 자료구조 중 하나입니다. 그리고 모든 프로세스와 쓰레드 안에는 각각 저마다의 콜스택이 들어가 있습니다. 왜냐하면 일을 수행할 때마다 어디서 왔고 어디로 다시 가야 되는지 정보를 기억해야되기 때문입니다.
  • Stack: LIFO (Last In First Out) 제일 나중에 들어온 것이 제일 먼저 나가는 자료구조를 가진다.

프로세스마다 스택마다 지정된 콜스택 사이즈가 있는데 재귀함수 등을 잘못사용하게 되면 지정된 콜스택 사이즈를 초과할 수 가 있습니다.

브라우저 런타임 환경 (JavaScript Runtiem Environment)

자바스크립트만으로 할 수 있는 것이 한정적이지만 브라우저가 제공하는 웹 APIs를 이용해서 다양한 일들을 할 수 있습니다. 대표적으로 브라우저의 멀티쓰레딩을 이용해서 조금 더 다양한 일들을 동시에 실행할 수 있다. 예를 들면 fetch를 이용해서 백엔드에서 데이터를 받아 온다든지 setTimeout을 이용해서 일정 기간의 시간이 지난 다음에 우리가 등록한 콜백 함수를 실행하는 등 멋진 일들을 할 수 있습니다.

웹 API의 콜백함수가 콜스택에 쌓이고 실행이 되면 웹 API는 이것을 태스크큐로 보냅니다. 여기서 이벤트 루프는 콜스택과 태스크큐를 계속 지켜보고 있다가 콜스택이 다 비워지면, 자바스크립트 엔진이 더이상 일하고 있지 않으면, 태스크큐는 먼저 들어온 웹api 콜백 함수 1개를 콜스택으로 보내고 자바스크립트 엔진은 콜스택으로 들어온 콜백 함수를 실행시키게 됩니다.

그래서 이벤트 루프는 프로세스가 동작하는 동안 계속 빙글빙글 루프를 돌면서 콜스택이 비어져 있다면 태스크 큐에 들어 있는 아이를 콜 스택으로 가져와서 자바스크립트 엔진이 수행할 수 있도록 도와줍니다.
그리고 버튼에 클릭이 한번 더 일어나면 우리가 등록한 콜백함수가 또 태스크 큐에 들어옵니다. 그리고 콜스택이 비워지길 기다렸다가 콜스택이 비워지면 이벤트루프는 콜백을 하나를 콜스택에 가지고 옵니다. 이 태스크큐에 있는 콜백을 한 번에 하나만 콜스택으로 가져옵니다.

이런식으로 이벤트루프를 통해서 자바스크립트 엔진과 웹 APIs가 동작하게 됩니다.

우리가 자바스크립트 코드에 버튼에 클릭 리스터를 등록해놓으면 브라우저에서 클릭이 되면 클릭이라는 이벤트가 발생하게 되면 웹 APIs는 이벤트 루프에 우리가 등록한 콜백함수를 태스크 큐 안에다 넣습니다.

하지만 모든 웹 APIs 가 태스크 큐를 이용하는 것은 아니고 createElement, appendChild 등 보통 대게의 경우에는 이벤트루프를 이용하지 않고 바로 즉각 실행됩니다.

지금 실행되지 않고, 지정된 시간이나 지정된 업무가 완료되면 Task Queue로 가는 경우는 지금 당장 일을 수행하지 않아도 되는, 또는 다른 이벤트를 기다리는 경우에 발생할 수 있습니다. (이런 것을 처리 하기 위해 Task Queue 가 만들어졌다)

  • Queue(FIFO First In First Out): 처음에 들어온 아이가 제일 처음으로 나가는 자료구조입니다. 큐의 대표적인 api로 add 와 remove가 있다. add(1), add(2) 를 한 후 remove() 를 하게 되면 1이 큐에서 빠져나가게 됩니다.

Task Queue

태스크큐는 웹 APIs 에서 우리가 등록한 콜백함수를 특정한 이벤트가 발생했을 때, 즉 지정된 이벤트가 발생했을 때 태스크 큐에 보낸다.

Microtask queue

마이크로 태스크 큐는 우리가 흔히 쓰는 프로미스에 등록된 콜백, 즉 프로미스가 다 수행이 되고 나면 그 다음에 등록한 then에 등록한 콜백함수, 그리고 mutation observer 라는 웹 API 중 하나가 있는데 거기에 등록된 콜백, 이것들이 마이크로 태스크 큐에 들어온다.

즉 프로미스를 사용하면 프로미스에 등록된 콜백이 여기에 들어오게 된다.

Render

주기적으로 브라우저에서 우리가 요소들을 움직이거나 애니메이션을 할 때, 주기적으로 브라우저에 업데이트 해줘하는데 그때 주기적으로 화면에 업데이트 해주는 것이 render 이다

웹 APIs 중 하나인 리퀘스트 애니메이션 프레임이라는 API가 있는데 이 API를 통해서 콜백을 등록해 놓으면 다음에 브라우저가 업데이트 되기 전에 내 콜백을 실행해줘 라는 API가 있다 이제 그때 우리가 호출하는 콜백은 이 리퀘스트 애니메이션 프레임 이 큐에 쌓인다.

즉 렌더는 브라우저에서 우리가 변형한 코드가 주기적으로 업데이트 되기 위해서 주기적으로 호출되는 순서인데 그 전에 리퀘스트 애니메이션 프레임이라는 API를 부르면 그때 우리가 등록한 콜백은 이 리퀘스트 애니메이션 프레임 큐에 차곡 차곡 쌓인다.

그럼 브라우저는 어떻게 자바스크립트 엔진과 웹 APIs 를 동시에 순서적으로 실행하고 처리할까

이벤트 루프는 while(true) { // do something } 처럼 빙글 빙글 도는 루프 중에 하나이다.

  • 이벤트 루프는 루프를 돌다가 Call Stack 에 function 이 있으면 함수가 끝날 때까지 꼼짝도 하지 않고 여기에서 기다린다.
  • 그래서 만약 콜스택에서 굉장히 오래 걸리는 함수가 실행된다면 사용자에게 더이상 화면이 업데이트 되지 않게 된다. 그리고 다른 클릭 이벤트가 발생해도 그 클릭에 등록된 콜백함수가 실행되지 않는다. 왜냐 이벤트루프가 계속 콜스택에 머물러 있기 때문
  • 그리고 콜스택에 있는 함수 실행이 종료되면 다시 돌기 시작한다.

다음으로 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 업데이트가 일어나기 전에 코드가 실행 된다.

profile
Seize the day

0개의 댓글