Skip to content

이벤트루프 콜스택이 무엇인가

Dia edited this page Apr 2, 2019 · 1 revision

번역 : devhaeyeon

원문 출처 : https://gist.github.com/jesstelford/9a35d20a2aa044df8bf241e00d7bc2d0

Regular Event Loop (규칙적인 Event Loop)

JavaScript의 Call Stack, Event Loop 및 JS 실행 환경에 제공되는 비동기 API가 주어진 실행 순서를 보여준다. (이 예제에서는 브라우저 환경의 웹 API를 다룬다.)

이 코드가 주어진다.

setTimeout(() => { 
  console.log('hi')
}, 1000)

Call Stack, Event Loop, Web API들은 다음과 같은 관계를 가진다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

시작하려면 모든 것이 비어 있어야 한다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

코드가 실행하기 시작하고, 그 내용들을 Call Stack(여기 <global>)에 밀어넣는다. (쌓여진다)

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
> setTimeout(() => {  | <global>          |              | |               |
    console.log('hi') | setTimeout        |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

그러고나서 첫번째 줄이 실행되어 진다. 이렇게 하면 함수 실행이 두번째 항목으로 Call Stack에 푸시된다.

Call Stack은 stack이다. 마지막에 쌓여진 항목이 첫번째로 나간다. 일명 Last In, First Out이라 하고 쌓여진 접시 더미를 생각하면 된다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
> setTimeout(() => {  | <global>          |              | | timeout, 1000 |
    console.log('hi') | setTimeout        |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

setTimeout을 실행하면 실질적으로 JS의 일부가 아닌 외부에서 코드가 호출이 된다. 이것은 브라우저에서 제공하는 Wep API에 해당하는 부분이다. 노드에서 사용할 수 있는 다른 API 세트들이 존재한다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          |              | | timeout, 1000 |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

setTimeout은 시간이 초과 되면 실행을 완료한다. 이것은 요청한 시간을 기다리는 Web API에 오프로드를 한다.

참고 : 오프로드는 컴퓨터 시스템에서 동일한 작업을 수행할 수 있는 장치가 여러개 존재하는 경우 비교적 작업량이 적게 할당 되어 있는 장치에서 작업량이 많은 장치의 일부를 받아서 처리 하는 것이다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | | timeout, 1000 |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

더 이상 실행할 JS의 line이 없으므로 Call Stack은 비어있다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   | function   <-----timeout, 1000 |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

timeout이 만료되면 Wep API는 Event Loop에 코드를 더함으로써 JS 를 알 수 있다.

이미 실행 중인 코드를 사용할 수 있으므로 Call Stack에 밀어넣지 않는 이상한 상황에 놓이게 된다.

Event Loop는 Queue(대기열)이다. 첫번째 들어온 것이 먼저 나간다. 일명 First In, First Out 이라 하고, 영화의 대기줄을 생각하면 된다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function        <---function     | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

Call Stack이 비어 있을 때마다 JS 실행 환경은 Event Loop에 대기하고 있는 것들이 있는지, 없는지 보기 위해 체크한다. 해당 항목이 있으면 첫번째 항목은 해당 항목을 실행하기 위해 Call Stack으로 이동한다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function          |              | |               |
>   console.log('hi') | console.log       |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

함수가 실행되면 console.log가 호출되고 Call Stack에 쌓이게 된다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function          |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
> hi

실행이 끝나면 hi가 콘솔에 보여지게 되고, console.log는 Call Stack에서 제거 되어 진다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
> hi

마지막으로 함수는 다른 어느 것도 실행할 명령어들이 없으므로 호출 스택은 끝이 난다.

우리의 프로그램은 이제 실행이 종료된다.

끗.

Starved Event Loop (굶주려 있는 Event Loop)

현재 Call Stack에서 실행되는 코드가 Event Loop 코드를 실행하는 것을 방지하는 방법의 예이다. Event Loop은 굷주려 있다.

이 코드가 주어진다.

setTimeout(() => { 
  console.log('bye')
}, 2)           
someSlowFn()
console.log('hi')
        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | |               |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

시작하려면 모든 것이 비어 있어야 한다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          |              | |               |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

코드가 실행하기 시작하고, 그 내용들을 Call Stack(여기 <global>)에 밀어넣는다. (쌓여진다)

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
> setTimeout(() => {  | <global>          |              | |               |
    console.log('bye')| setTimeout        |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

setTimeout은 Call Stack에 쌓인다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
> setTimeout(() => {  | <global>          |              | | timeout, 2    |
    console.log('bye')| setTimeout        |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

setTimeout은 Web API의 timeout을 트리거한다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          |              | | timeout, 2    |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

setTimeout은 실행이 완료되고, Web API는 요청된 시간(2ms)을 기다린다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          |              | | timeout, 2    |
    console.log('bye')| someSlowFn        |              | |               |
  }, 2)               |                   |              | |               |
> someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

someSlowFn 실행을 시작한다. 이 함수가 완료하는데 약 300ms가 걸린다고 가정한다. 300ms의 경우 JS는 Call Stack을 제거할 수 없다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          | function   <-----timeout, 2    |
    console.log('bye')| someSlowFn        |              | |               |
  }, 2)               |                   |              | |               |
> someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

한편 timeout은 만료 되었고, Wep API는 Event Loop에 코드를 더함으로써 JS 를 알 수 있다.

someSlowFn은 여전히 Call Stack에서 실행되고 있고, 중단할 수 없기 때문에 timeout된 이 코드는 Event Loop에서 차례를 기다린다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          | function     | |               |
    console.log('bye')| someSlowFn        |              | |               |
  }, 2)               |                   |              | |               |
> someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

someSlowFn이 끝나길 기다림...

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          | function     | |               |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
> someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

someSlowFn이 끝났다!

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          | function     | |               |
    console.log('bye')| console.log       |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
> console.log('hi')   |                   |              | |               |

그 다음줄을 실행하고 Call Stack으로 console.log을 쌓는다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          | function     | |               |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
> console.log('hi')   |                   |              | |               |

> hi

console.log덕분애 콘솔에 hi가 찍힌 것을 볼 수 있다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   | function     | |               |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

> hi

남은 실행이 없음으로 특별한 <global>도 Call Stack에서 빠져 나간다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function        <---function     | |               |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

> hi

이렇게 하면 JS실행환경이 활성화 되어 실행해야 하는 코드가 Event Loop를 확인한다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function          |              | |               |
>   console.log('bye')| console.log       |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

> hi

함수를 실행하면 console.log가 호출되고, Call Stack로 밀어 넣어진다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function          |              | |               |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

> hi
> bye

실행이 끝나면, bye는 화면에 보여지고, console.log는 Call Stack으로 부터 제거 되어 진다.

여기까지의 코드가 원래 setTimeout을 요청한 후 최소 300ms이다. 즉, 2ms만 지나면 실행되도록 요청했지만, Event Loop의 setTimeout코드가 실행되기 전에 Call Stack이 비워지길 기다려야 했다.

비록 우리가 someSlowFn을 가지고 있지 않더라도 setTimeout은 일부 case 에서 최소 지연이 허용 됨에 따라 setTimeout은 고정된다.

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | |               |
    console.log('bye')|                   |              | |               |
  }, 2)               |                   |              | |               |
  someSlowFn()        |                   |              | |               |
  console.log('hi')   |                   |              | |               |

> hi
> bye

마지막으로 함수는 다른 어느 것도 실행할 명령어들이 없으므로 호출 스택은 끝이 난다.

우리의 프로그램은 이제 실행이 종료된다.

끗.

Microtask queue을 통해 Promise와 함께 Event Loop을 굶게 하는 것이 가능합니다.


참고하면 좋은 자료

post

video

code

Clone this wiki locally