1. 동기와 비동기? 이벤트루프와 태스크 큐?

00. 들어가기 전에

들어가기 전에 여러분이 복습하셔야 하는 개념입니다.

  1. 콜백함수(어제 배운 코어 자바스크립트의 콜백함수 부분을 훑어보고 와주세요!)

  2. setTimeout() 링크를 들어가서 간단한게 훑어보고 와주세요!

setTimeout(() => {console.log("첫 번째 메시지")}, 5000);
setTimeout(() => {console.log("두 번째 메시지")}, 3000);
setTimeout(() => {console.log("세 번째 메시지")}, 1000);
console.log("네 번째 메시지")

// 콘솔 출력:

// 네 번째 메시지
// 세 번째 메시지
// 두 번째 메시지
// 첫 번째 메시지

위와 같은 코드를 우리는 이번장에 살펴보려고 합니다.

01. 동기와 비동기의 개념

자바스크립트 엔진은 싱글 스레드로 동작 합니다. 우리가 프로세스와 스레드와 같은 개념을 아직 모르지만 그래도 퍽 직관적인 부분이 있어서, 설명해보려고 합니다.

프로그램이란 어떤 작업을 위해 실행할 수 있는 파일을 의미하며, 해당 파일이 실행되면, 하나의 프로세스가 실행되게 되어 운영체제로부터 시스템 자원(메모리와 연산능력)을 할당받게 됩니다. 이러한 자원을 통해 프로그램이 컴퓨터에서 연속적으로 프로세스를 실행 할 수 있게 되는 것이며, 프로세스란 보통 '실행된' 프로그램을 이야기합니다. 여기서 하나의 프로세스내에서 실행되는 흐름의 단위로 스레드가 있습니다. 프로세스가 할당 받은 자원의 일부를 할당받거나 공유하며 실제로 여러분의 프로그램이 수행을 한다고 생각하시면 좋습니다. 일반적으로 쓰레드는 "일꾼"으로 비유가 됩니다.

여기서 프로세스가 수행할 코드는 프로그래밍 언어로 작성되어 기계어로 번역되어 순차적으로 수행되는 것이고, 이 과정이 컴파일이라고 우리는 배웠습니다. 여기서 자바스크립트의 특징적인 특징이 하나 더 추가되는데, 일반적으로 프로그래밍 언어로 작성된 프로그램이 프로세스가 되기 위해서는 "운영체제(mac os, windows)"로부터 시스템의 자원을 할당받아야 하는데, 사실 자바스크립트에서는 프로세스와 운영체제 사이에 "브라우저(safari, chrome, window edge)"가 있다는 것을 이해해주시면 될 것 같습니다.

결론적으로 자바스크립트가 싱글 스레드로 동작한다는 것은, 하나의 일꾼을 가지고 우리가 작성한 코드를 읽고 수행해 나간다고 생각해주시면 될 것 같습니다.

싱글 스레드로 작업을 수행하는 것에는 다양한 장점과 단점이 있지만, 모든 것들을 다 언급하거나 설명하기는 분량이 너무 많아 가장 직관적인 장점과 단점을 설명하면, 장점으로는 프로그래밍의 난이도가 상대적으로 쉽고, 일반적인 상황에서 멀티 스레드로 작업하는 것보다 비용이 적고, 빠르다는 것이 있습니다. 반대로 단점으로는 연산량이 많거나 구조적으로 시간이 걸리는 작업을 하는 경우, 그 작업이 완료되어야 다른 작업을 수행할 수 있다는 것과 에러리에 신경을 더 써야하는 부분이 있습니다.

그렇다면 연산량이 많거나 구조적으로 시간이 걸리는 작업을 하는 경우란 어떤 경우가 있을까요? 특히 연산량이 많은 작업이라는 것은 이해가 가는데, 구조적으로 시간이 걸리는 작업이라는것은 어떤 것이 있을까요? 그리고 그러한 작업이 끝나야 다른 작업을 수행해야 한다는 것이 왜 단점으로 작용한다는 것 인지 아직 이해가 가지 않으실겁니다.

첫 번째로 연산량이 많거나 구조적으로 시간이 걸리는 작업 들의 예시는, 보통 "렌더", "통신" 등에서 많이 발생합니다. 전자는 말 그대로 연산량이 많아서 시간이 소요되고, 통신같은 경우는 스레드가 다음 줄을 읽어나갈 준비가 되는 동안 통신과 관련한 코드의 응답이 오지 않았다면 기다려야 하는 상황이 오는거죠.

// Some code

let 헤드라인들;

function getDataFromServer() {
    함수야 저기 www.naver.com에 가서, 
    거기에 저장되어있는 뉴스 헤드라인좀 쭉 받아와줘,    
    return [네이버 서버에서 받아온 헤드라인들];
}

function renderNewsHeadLine(headLineArr) {
    함수야 헤드라인들이 들어있는 배열이 들어오면,
    html <div/> element를 이렇게 저렇게 예쁘게 만들어서,
    갯수만큼 화면에 뿌려줘!
    return;
}

헤드라인 = getDataFromServer();
renderNewsHeadLine(헤드라인);

위와 같은 상황은 매우 자주 일어나는 상황입니다. 실제로 위의 프로그램을 실행시키면 어떻게 될까요? 정답은 아무것도 렌더되지 않는다 입니다. getDataFromServer()라는 함수가 실행되고 해당 요청을 보내고 응답을 기다리는 동안, 우리의 cpu는 다음 줄을 읽어내려갈 준비가 되고도 남았고, 실제로 그냥 읽어내려갔기 때문이죠. 결국 renderNewsHeadLine() 함수는 빈 배열을 건내받았기 때문에, 아무것도 렌더되지 않고 끝나게 됩니다.

이러한 상황을 해결하려고 처리를 해두는 것이 비동기 처리 입니다!

위의 코드 예시에서처럼, 만약 우리가 해당 데이터가 온 것을 확인하고, 작업을 이어나가게끔 신경을 써 뒀더라도 위의 카페에서는 모든 주문이 처리되는데 걸리는 시간이 지나치게 오래 소요 될 것 입니다. 고객들은 불필요한 대기를 이어나가야 하죠. 그리고 여러분들도 일을 해보셨다면 아시겠지만, 보통 고객은 참지 않습니다!

그래서 비동기 처리의 개념이 등장하게 됩니다. 2번째 그림의 상황이죠. 시간이 오래 걸리지 않는 주문을 받는 일은 몰아서 처리를 해뒀습니다. 그리고 커피의 제조같이 시간이 소요되는 일은 뒤에서 담당을 해주고 있죠. 프로그램은 주문을 받아주고 주문이 완료되면 다시 해당 고객을 불러 커피를 건내줍니다. 이제 고객이 무의미하게 기다리는 시간은 사실상 없는 수준으로 점원의 처리 속도만큼 빨라지게 되었네요!

...와 같은 의문이 드신다면 너무 잘 따라오고 계신겁니다!

맞습니다. 뒷방에서 커피를 제조를 하는 놈도, 서버로 네이버 헤드라인이 담긴 배열을 보내달라고 요청을 보내놓고 기다리는 놈도 모두 스레드입니다. 실제로 일을 열심히 하고 있는 중이죠. 그런데 여전히 자바스크립트는 싱글 스레드로 동작하는 것도 맞습니다. 어떻게 된 일일까요?

정답은 브라우저에 있었습니다! 브라우저는 사실 여러분이 사용하시는 것보다 복잡하고 똑똑한 "프로그램"입니다. 다른 서버나 네트워크와 통신기능도 탑재되어있고, 여러분이 보고계시는 html, css를 그려주는 기능도 탑재되어 있고, 내부적으로 자바스크립트 언어로 작성된 프로그램을 돌릴 수 있는 운영체제 비스무리한 기능도 있죠. 그래서 결론적으로 조금 더 정확하게 말하면, 자바스크립트 엔진은 싱글 스레드로 동작하지만, 비동기 처리를 하기 위해 있는 별도의 스레드들이 있다! 정도로만 이해해주시면 좋을 것 같습니다.

02. 카페 예시 조금 더 들여다보기

실제 우리 자바스크립트 코드의 런타임은 다음과 같이 구성되어 있습니다. 각각의 구성 요소에 대한 설명은 아래의 출처에 나와 있으니 들어가서 꼭 읽고 돌아오셔야 합니다.

읽고 오셨다면, 대충 어떤 것들이 어떻게 동작하는지는 알게 되셨을 것 같으니 우리의 카페 예제를 다시 복습해보려고 합니다.

먼저 우리의 coffee api는 다음과 같이 구성되어 있다고 가정합니다. 그리고 callStack의 경우 우리가 처음 벽을 느끼면서 배웠던 "그 녀석" 실행 컨텍스트에서 봤던 콜스택이 기억나시죠? 실제로 저기가 실행 컨텍스트입니다. 이벤트 루프는 말이 어렵지만, 사실 그냥 무한 loop를 돌면서 콜스택이 비어있는지, Task Queue가 비어있는지를 확인하고 콜스택이 비어있는데, 태스크 큐에 뭔가의 콜백이 남아있다면 콜스택에 밀어주는 딱 그역할을 한다고 생각하시면 됩니다.

  1. 당연하게도 함수가 실행되면 자바스크립트 엔진은 하던대로 콜스택에 쌓습니다. 사실 비유를 위해 한번에 작성하기는 했지만, 하나의 함수가 호출되면 해당 실행 컨텍스트가 콜스택에 올랐다가, 사라지고를 반복하겠죠?

2. 두 번째로 자바스크립트 엔진은 매우 빠르게 해당 코드들을 수행합니다. 물론 각각의 코드에는 커피를 만들라는 함수들이 있었네요! 뒷방에 있는 스레드들이 유능한지 모든 업무가 쌓여 콜백이 태스크큐에 들어갑니다.

3. 그 다음에 이벤트 루프가 실제로 일을 하게 됩니다

03. 아직 불편합니다! 왜 "콜백"이죠?

사실 이 부분은 완벽하게 설명하려면 너무 긴 부가 설명이 필요합니다. 그래도 내용을 이해하려면, 우리가 특정 작업을 비동기적으로 처리하려면 당연하게도 컴퓨터에게 이 작업이 비동기로 처리될 것이라고 알려줘야하고, 그 작업이 처리된 이후에야 할 수 있는 작업들을 적어둬야 합니다. 여기서의 콜백은 "지금 뒷방에서 작업이 끝났으니까 만약 돌릴 수 있으면 아까 거기로 돌아가서 작업 이어서 해!" 에서의 "아까 거기로 돌아가"와 같은 것 이라고 생각하시면 됩니다.

충실하게 아까 setTimeout()의 mdn 문서를 읽어오셨다면, 해당 함수가 비동기 함수이며, 콜백함수를 인자로 넘겨준다는 이야기를 듣고 오셨을 겁니다.

// Some code
setTimeout(() => {console.log("첫 번째 메시지")}, 5000);
setTimeout(() => {console.log("두 번째 메시지")}, 3000);
setTimeout(() => {console.log("세 번째 메시지")}, 1000);
console.log("네 번째 메시지")

// 콘솔 출력:

// 네 번째 메시지
// 세 번째 메시지
// 두 번째 메시지
// 첫 번째 메시지

그래서 보통의 비동기 함수들은 다음과 같이 생겼습니다.

// 아무튼 sudo code...

function setTimeout(callBackFunction, time) {
    // 실행되면 뒷방 스레드한테 가서,
    // time 만큼 째고 있다가,
    // 콜백으로 callBackFunction넘겨서,
    // 콜스택에서 실행해!
}

04. 마무리

"자바스크립트는 싱글 스레드이며, 비동기처리를 지원한다"라는 말을 조금 더 이해하실 수 있으면 좋겠습니다.

이번 장에서 배울 내용의 핵심 아이디어이며, 이번 장을 따라가는데, 필수적으로 이해해야 하는 개념이기 때문입니다.

사실 위와 같은 내용만 이해가 된다면, 나머지는 그저 명세에 적힌 문법들을 그 때 그 때 찾아서 쓰면 그만입니다.

Promise도 결국은 위와 같은 처리를 하기 위한 "약속"일 뿐이고, 그 비동기 처리 이후에 할 "콜백"들을 지정해주는 것 일 뿐이고,

심지어 async, await는 위의 Promise를 더 쉽게 사용하는 하나의 방법에 불과하니까요!

Last updated