자바스크립트 핵심개념 3탄 (호출스택과 이벤트 루프 개념에 대해)

이병수·2020년 10월 29일
2
post-thumbnail

오늘은 자바스크립트의 핵심원리 세 번째편 콜스택과 이벤트 루프에 대하여 알아보자🥁👌

호출 스택과 이벤트 루프는 자바스크립트 코드 동작원리를 이해하는 데 필수라고 하니 꼭 알아두자!


자바스크립트 싱글스레드

자바스크립트는 기본적으로 싱글 스레드 프로그래밍 언어(Single threaded programming language)이다.

이 말은 한 번에 하나의 작업만 할 수 있다는 뜻이다.

one thread == one call stack == one thing at a time

여기서 call stack은 무엇일까?


호출스택(call stack)

호출스택(call stack)은 프로그램 상에서 우리가 어떤 순서로 작업을 수행하는 지 기록하는 작업스케줄링과 관련된 자료 구조이다.

  • 우리가 어떤 함수를 실행하게 되면 우리는 그 함수를 스택의 맨위에 놓는데, 이것을 push라고 한다. 또한 함수가 어떤 값을 리턴하거나 실행을 종료할 경우 그 함수를 스택 맨위에서부터 꺼내는데 이것을 pop이라고 한다.

예제(1)

function first() {
  second();
  console.log('첫 번째');
}
function second() {
  third();
  console.log('두 번째');
}
function third() {
  console.log('세 번째');
}
first();
third();

(first() 가 호출되었을 때)

  • first가 호출 되고 first 안의 second가 호출되고 second안의 third가 호출된다.
  • 여기서 실행은 호출순서와 반대로 일어난다. 실행되면 쌓여진 더미(stack)에서 하나씩 제거되는데 이러한 구조 특성이 후입선출(LIFO) 구조라고 하며 이 것을 호출스택이라고 부른다.
  • third부터 first까지 실행되고 지워지는 과정을 거쳐 마지막으로 전역 컨텍스트 main이 지워진다. 글면 호출스택에는 아무것도 남지 않게되고 이러면 실행완료이다.

예제(2)
어떤 순서로 스택이 되는지 한 번 생각해보자!

function multiply(a,b) {
  return a*b;
}
function square(n) {
  return multiply(n,n);
}
function printSquare(n) {
  var squared = square(n);
  console.log(squared);
}

printSquare(4)

[PUSH]

위 코드를 실행하면 일단 저 함수들을 감싸고 있는 main() 함수를 스택에 넣는다.

그리고 printSquare(4) 함수가 실행되므로 printSquare(4)를 스택에 넣는다.

printSquare() 함수 내부에서 square(n) 함수를 실행하므로 스택에 square(n)을 넣는다.

square(n) 함수 내부에서 실행되는 multiply(a, b)를 스택에 넣는다.

[POP]

드디어 처음으로 리턴이 되었다. multiply(a, b)가 값을 리턴하며 스택에서 나온다.

square(n)도 값을 리턴하고 나온다.

printSquare(n)에서 console.log(squared)를 스택에 push하고 실행한 후 다시 빠져나온다.

printSquare(n) 함수가 종료되었으므로 stack에서 나온다.

main() 함수가 종료되었으므로 스택에서 나온다.


비동기콜백

자바스크립트는 이렇게 싱글 스레드 언어이기 때문에 함수를 실행하면 함수 호출이 순차적으로 쌓이고 스택의 맨 위에서부터 차례대로 한 번에 하나의 함수만 처리할 수 있다!
하지만 잠깐!

간단한 프로그램이면 상관없지만 만약 아주 복잡한 프로그램을 구동한다고 생각해보자! 시간이 매우 오래 걸리는 작업이 스택에 쌓이고 실행되면 그 다음 작업은 무한정 대기할 수밖에 없다.

이렇게 다른 작업을 실행하기 위해서 이 전 작업이 완료될 때까지 기다려야만 하는 상황을 블로킹(blocking)이라고 한다
왜 이것이 문제가 되냐면 어떤 작업이 실행되어 동작하고 있는 동안에는 브라우저가 다른 일을 전혀 할 수 없으므로 잠시 먹통이 되기 때문이다. 만약 이렇게 브라우저가 블락되는 순간이 잦다면 사용자의 불만은 점점 커질 것이다.

이러한 점을 극복하기 위한 해결방안이 바로 Asynchronous Callbacks(비동기 콜백)이다!

자바스크립트가 싱글 스레드 언어임에도 불구하고 우리가 웹 사이트에서 끊김없이 여러 작업을 동시에 할 수 있는 것은 바로 브라우저가 Web APIs 같은 것들을 제공하여 비동기 작업을 가능하게 해주기 때문이다.

동작원리

  1. 함수를 동기 호출하게 되면 call stack에 차곡차곡 쌓여 순차적으로 실행된다.
  2. 이 때 만약에 우리가 AJAX나 setTimeout 혹은 DOM event를 실행하면 자바스크립트 엔진은 call stack에서 Web APIs로 보내고 정해진 시간 혹은 이벤트가 발생한 순간에 순차적으로 callback queue에 적재한다.
  3. callback queue에 줄을 선 함수들은 call stack에 쌓여있던 것들이 모두 제거되어 깨끗해지면 차례대로 스택에 쌓여서 실행되게 된다.

큐(queue)
여기서 queue는 지난 블로그에서 다뤘지만 잠깐 언급하면 큐는 스택(Stack)과 같이 자료 구조의 일종이다. 한 쪽에서만 삽입과 삭제가 이루어졌던 스택과는 달리 한쪽에서는 삽입이 되고 다른 한쪽에서는 삭제 작업이 이루어지는 자료 구조이다.
가장 먼저 삽입된 자료가 가장 먼저 삭제되는 구조이므로 선입선출(FIFO: First In First Out)이라고도 부른다.


이벤트루프

예제를 통해 이벤트 루프의 로직을 이해해보자

예제(1)

console.log(1);
setTimeout(function cb() {
  console.log(2);
},5000);
console.log(3);
  1. 먼저 main()함수가 실행되고, console.log(1)이 스택에 쌓이고 실행되어 콘솔창에 출력된다.
  2. setTimeout의 콜백함수 cb가 스택에 쌓이는데 자바스크립트 엔진에서 처리하지 않고 바로 web APIs로 넘긴다. 그러면 브라우저는 마치 setTimeout 함수가 완료된 것처럼 스택에서 pop하고 다음 작업을 진행한다.
  3. console.log(3)이 스택에 쌓이고 출력된 후 사라진다.
  4. 모든 코드가 실행되었으므로 main()함수가 스택에서 제거된다.
  5. 5초동안 대기하고 있던 cb함수가 5초가 지난 시점에 task queue에 들어온다.
  6. stack이 비어있으므로 cb함수를 stack에 적재하고 console.log(2)를 실행한다.

위 로직을 이 사이트에서 꼭 실행해보자

위의 경우 setTimeout이 5초 후에 실행하는 코드이기 때문에 순서가 1-3-2의 순서로 명확하지만 만약 setTimeout 함수를 0초 후에 실행하도록 코드를 변경하면 어떨까?

console.log(1);
setTimeout(function cb() {
  console.log(2);
},0);
console.log(3);

이 경우에도 결과는 다르지 않다.

그 이유는 task queue에 줄 서 있는 callback 함수들은 stack이 비어있을 때만 stack으로 이동할 수 있기 때문이다.

위 경우 setTimeout으로 설정한 cb함수는 web APIs로 이동하는 즉시 task queue로 이동하게 되는데, stack이 비어있지 않기 때문에 대기 상태로 있게되고 console.log(3)이 출력되고 스택이 비워지면 이동하여 console.log(2)가 실행된다.

이벤트루프는 호출 스택을 주시하고 있다가 호출 스택이 비워지면(전역 컨텍스트 main 실행이 종료되면) 태스크 큐에서 함수를 하나씩 호출 스택으로 밀어 올리는 역할을 한다.


참고사이트
1. https://www.zerocho.com
2. https://im-developer.tistory.com/113

0개의 댓글