이전글에서는 자바스크립트 엔진이 js를 어떻게 읽고 해석하는지에 다뤘습니다.
해당 글은 구성요소와 이벤트루프가 어떻게 돌아가는지에 대한 글입니다.
V8은 single thread으로 되어있는 실행 엔진이고 상당히 많은 일을 한다.
js코드를 해석, 실행도 하며 callStack을 다루며 memory 할당도 관리하고 GC도 하며 .. 등등을 담당한다.
뭐 엄청나게 많은 일을 하지만 여기선 이벤트루프 관련 글이기에 해당 부분만 다룬다.
메모리들을 저장하는 영역인 Heap, 함수의 호출시점을 기억하는 Call Stack으로 이루어져있다.
함수나 변수들을 저장하는 공간.
C의 경우 개발자가 할당한 메모리에 대해선 해제를 해주어야 leaks가 발생하지 않지만, JS나, Java와 같은 고-급언어들은 Garbage Collector가 알아서 해준다.
js는 mark and sweep의 방식을 쓴다고 알고있다. 그렇다고 또 이게 만능은 아닌,,
V8을 사용하는 브라우저나 node.js같은 런타임 환경이 single thread인 것은 아니다.
V8은 thread가 하나로 이루어져있다고 했다. 한번에 하나의 일만 처리할 수 있는 것이다.
프로그래밍언어의 함수 동작은 Stack 형식으로 진행된다.
❓ Stack과 함수동작의 상관관계가 잘 이해가 되지 않는다면 cpu관점에서의 함수호출을 보면 도움이 된다.
무튼, thread가 하나면 call stack이 1개라고 이해할 수 있다.
javascript가 single thread 언어라고 표현되는 건,
js를 해석 및 실행하는 V8이 single thread이라서 한번에 하나의 함수만 처리하는 call stack의 형태로 실행되기 때문이다.
call stack이 동작하는 방식 - 이전 호출 순서를 기억해두었다가 종료시 pop하고 나머지 코드를 진행함
stack이 넘치면 stack overflow. - 재귀에서 탈출조건을 주어야하는 이유다
한번에 여러일을 처리하지 못하는 Single thread면 발생할 수 있는 문제가 하나있다.
엄청난 시간이 소요되는 코드가 돌아갈 때 다음 코드들은 작업이 완료되기까지전 매우 오래 기다려야한다.거의 교착상태
프로그램에서 우리가 흔히 사용하는 브라우저로 대상을 옮겨보자.
농담곰 사진을 보기위해 사진을 클릭을 했다.
그러나 누군가 코드를 잘못짜서 이 클릭이벤트를 처리하는데 10초씩 걸린다.
그럼 10초가 지날 때까지 다음 코드들은 실행되지 못하므로 (blocking) 유저는 10초동안 아무것도 못한다.
분명 single thread면 충분히 발생할 수 있는 상황이다.
그렇지만 우리는 그런 걸 경험해본적 없다.
어떻게 그럴까? 바로 비동기 작업을 통하기 때문이다
쉽게 말하면, 오래걸리는 일을 브라우저에게 던져놓고 나머지 동기적인 연산은 call stack이 하나인 자바스크립트 엔진이 하는 것이다.
완벽한 분업!
이렇게 동작하기에 JS는 non-blocking되는 single thread기반의 비동기 처리를 하는 언어 라고 표현할 수 있겠다.
그렇다면 비동기작업은 어떻게 처리가 될까 ?
자바스크립트 엔진이 모든 작업을 도맡아하지 않는다.
비동기작업을 도와주는 브라우저라는 든든한 친구가 있기 때문이다.
사진에 보다시피 V8 이외 작업에 관여하는 요소들이 보인다.
저 브라우저의 요소들이 각각 어떤 방식으로 비동기작업을 도와주는지 살펴보자
브라우저에서 제공하는 작업들이다. 흔히 사용하는 setTimeout, Intersection Observer 등,, 엄청 많다.
Web API에서 직접적인 작업(오래걸리는 작업들)이 처리되고 이후, 전달된 call back이 Callback Queue로 들어가게된다.
setTimeout의 경우 특정 시간 이후 함수를 실행하는게 아니라 특정 시간 이후 callbackQueue로 callback을 옮기는 과정이다.
Queue는 들어온 순서대로 나간다 - 게임 큐 잡히는거 생각하면 편함.같은거임
말그대로 callback이 들어온 순서대로 실행되기 위하여 callStack으로 나간다.
언제 어느시점에 나가느냐를 결정짓는 것은 Event Loop가 담당한다.
Event loop의 역할은 Call Stack, Callback Queue를 감시하는 것이다.
Call Stack이 비어있을 때 Queue에서 callback을 Call Stack에 넣는다.
끝 ? -> 아, 네 끝.
이 아니라 더 알아야할 것들이 있다.
사실 Callback Queue는 하나가 아니다.
macro, micro task queue로 나뉜다
이 Queue는 Call Stack으로 들어갈 우선순위가 정해져있다.
우선순위에 따라서 Event Loop가 해당하는 callback을 call stack으로 넘겨주는 역할을 한다.
우선순위는 Micro > Macro 순이다.
gif에서 보듯이 Micro부터 call stack으로 올려주는 것을 확인할 수 있다.
그럼 각각의 queue에는 어떤 것이 들어가는지 간단하게 알아보고 마무리 퀴즈를 통해 확실히 이해했는지 확인해보자!
Promise callback
, process.nextTick, queueMicrotaskEvent Loop에 대해 다시 정리해보자면,
single thread의 비동기처리를 위한 역할이고 이런 처리를 할 수 있는 구동방식은 다음과 같다.
Callback Queue(2가지)와 Call Stack을 감시하며 Call Stack이 비었을 때 먼저 Micro Queue의 가장 오래된(제일 앞) Callback을 꺼내어 Call Stack에 올리고, 이후 Macro의 Callback을 Call Stack에 올린다.
Promise가 set* 함수보다 먼저 Call Stack에 올라가게 되는 이유가 바로 Event Loop의 우선순위 때문이라는 것을 알고 있자.
정답은 댓글에 있으니 풀어보고 확인해주세요 !
console.log('Start');
setTimeout(()=> {
console.log('setTimeout 0')
}, 0)
Promise.resolve()
.then(() => {console.log('Promise 1')})
.then(() => {console.log('Promise 2')})
.then(() => {
setTimeout(()=>{
console.log('Promise 3 in setTimeout')
}, 0)
})
console.log('End');
//Start
//End
//Promise 1
//Promise 2
// setTimeout 0
// Promise 3 in setTimeout