비동기 프로그래밍...너는 누구냐
최근 c++을 이용하여 개발을 하다 웹에 관심을 가지게 되었고 자바스크립트를 주력 언어로 쓰고 싶었다. 하지만 큰 문제가 있었는데 프로그램이 내가 짠 함수의 흐름대로 실행되지 않았고 자기가 멋대로 실행한다는 것이 었다. 그러던 중 자바스크립트는 비동기를 이용한 언어라는 것을 알게 되었고 공부한 사실을 정리하고자 한다.
동기적, 비동기적이란?
동기란 A 프로그램일 일을 하고 있는 동안 종료되지 않은 상태이면 B 프로그램이 일을 할 수 없는 구조이다. 다시 말해 내가 짠 코드를 순서대로 프로그램이 돌아가는 구조이다. 이것은 요청한 곳에서 결과가 도착을 해야 프로그램이 돌아간다는 것을 뜻한다.
비동기란 A 프로그램이 일을 하고 종료되지 않은 상태 도중에 B 프로그램이 일을 할 수 있는 구조이다. 다시 말해 A 프로그램이 완료되지 않은 상태에서 B프로그램이 동작하고 있다가 A 프로그램이 종료되는 순간 A 프로그램으로 다시 돌아가는 구조이다. 이것은 요청한 곳에서 결과가 도착하지 않아도 프로그램이 돌아간다는 것을 뜻한다.
동기와 비동기의 차이를 택배에 비유하여 예를 들어보자
동기 방식
동기 방식으로 택배를 시킨다면 택배가 도착하기 전까지 아무런 일을 하지 못하고 택배가 도착하기만을 기다려야 한다.
void order() { cout << "택배를 시켰습니다.\\n"; } void arrived() { cout << "택배가 도착했습니다.\\n"; } void work() { cout << "일을 합니다.\\n"; } order(); arrived(); work();
택배를 시켰습니다.택배가 도착했습니다.일을 합니다.
비동기 방식
비동기 방식으로 택배를 시킨다면 택배가 도착하기 전에도 다른 일을 할 수 있다. 즉 택배를 기다리지 않아도 된다.
function order() { console.log('택배를 시켰습니다.') } function arrived() { setTimeout(() => { console.log('택배가 도착했습니다.') }, 0) } function work() { console.log('일을 합니다.') } order() arrived() work()
택배를 시켰습니다.일을 합니다.택배가 도착했습니다.
비동기 함수의 동작 원리
자바스크립트는 분명히 싱글 스레드 환경에서 동작한다. 싱글 스레드는 하나의 콜스택을 가진다. 이것은 하나의 프로그램이 하나의 코드만 실행한다는 뜻이다. 그러면 어떻게 스레드가 하나인데 여러 개의 함수를 따로 실행시킬 수 있을까?
자바스크립트의 엔진은 다음과 같다.

메모리 힙은 메모리가 쌓이는 공간이고 콜 스택은 함수들이 순차적으로 실행 순서에 맞게 쌓이는 공간이다. 자바스크립트 엔진은 seTtimeout과 같은 브라우저 api를 제공해주지 않는다. 브라우저 api가 제공된 방법을 알고 싶으면 자바스크립트 런타임 내부를 살펴야한다.
다음은 자바스크립트 런타임 구성이다.

자바스크립트 런타임은 자바스크립트 엔진과 더불어 web api와 이벤트 루프, 콜백 큐를 담은 모든 것을 통틀어 말한다. web api는 브라우저 엔진으로 call stack에서 실행된 비동기 함수를 호출하고 callback queue에 넣는다. callback queue는 비동기적으로 실행된 콜백함수가 보관되는 영역이다. event loop는 call stack과 callback queue 상태를 체크하여 call stack이 비어있으면 callback queue에 첫번째 원소를 call stack으로 넣는다(tick).
call stack은 함수를 실행하기 위한 저장 장소이다. 함수를 실행하기 위해서는 반드시 call stack에 저장해야 한다.
function go() {
console.log('갑니다. 가요')
}
function ready() {
console.log('준비 중~')
go()
}
function main() {
ready()
}
main()
다음과 같은 코드에서 call stack은 이러하다

call stack과 중요하게 연결되는 개념은 blocking과 non-blocking 개념이다. blocking은 자신의 작업을 모두 마칠 때까지 호출한 함수에게 제어권을 넘겨주지 않고 대기하게 만든는 것을 의미한다. non-blocking은 호출된 함수가 바로 리턴해서 호출한 함수에게 제어권을 넘겨주고, 호출한 함수가 다른 일을 할 수 있도록 만들어 주는 것이다.
blocking을 브라우저에서 적용하게 된다면 사용자로 하여금 일을 순차적으로 처리하는 순간 순간을 대기하게 하여 지루하게 만들 수 있다. 따라서 우리는 브라우저 상에서 non-blocking을 이용하여야 하는데 여기서 우리가 가장 합리적으로 non-blocking을 이용할 수 있는 방식은 비동기 콜백이다.
비동기함수를 사용한 예이다. non-blocking을 이용한 방법이다.
console.log('hi')
setTimeout(() =>{
console.log('there')
}, 5000)
console.log('end')
비동기 함수를 이용한 call stack은 다음과 같다

자세히 살펴보면 setTimeout 내부에 console.log('there')는 call stack에 갑자기 저장되는 것을 알 수 있다. 그러면 어떻게 setTimeout은 내부에 콜백함수를 실행하는 것일까?
there의 갑작스런 등장은 이벤트 루프와 동시성의 역할로 설명이 가능하다. 자바스크립트는 한 번에 하나의 작업만이 가능하다. 자바스크립트는 다른 코드를 실행하는 동안 Ajax 요청 같은 일을 수행할 수 없다. 우리가 자바스크립트가 동시에 일을 처리한다고 느끼는 이유는 브라우저가 단순하게 런타임 이상의 작업을 해주기 때문이다. 자바스크립트의 WEB api는 자바스크립트에서 호출할 수 있는 스레드를 효과적으로 지원한다.
다음은 WEB api가 콜백을 처리하는 과정이다. 위의 내용에서 setTimeout 이후의 실행 과정만 가져왔다. 오른쪽 상단이 콜스택, 왼쪽 상단이 Web API, 하단이 테스크 큐이다.


비동기 함수의 내부 콜백함수는 WEB api로 들어가 내가 지정한 시간만큼 대기한다. 절대로 WEB api에서 call stack으로 직접 들어가는 일은 없다. 항상 WEB api에서 대기가 끝난 함수들은 항상 callback queue로 들어간다. callback queue에서는 call stack의 함수가 차 있으면 함수를 뽑아서 이동시킬 수 없다. call stack이 비어있다면 callback queue에서 맨 앞에 원소를 뽑아 call stack으로 삽입한다.
한 가지 팁은 setTimeout은 내가 대기해야 하는 최소 시간을 지정한다는 것이다. 항상 내가 정한 시간에 맞게 동작하지 않으니까 오류가 아니니 걱정하지 않아도 되겠다.
즉 결론은 우리는 UI를 생성할 때 call stack에 필요로 하지 않는 성능 저하 원인 코드를 넣는 행위는 자제해야 한다는 것이다. callback queue에서 항상 call stack에 빨리 접근할 수 있도록 call stack은 비워 놓는 편이 좋다는 것이다. 또한 callback queue도 항상 가득 채워놓지 말고 일정량이 들어오면 처리해주는 형식으로 코드를 짜는 것이 유저로 하여금 빠른 UI를 느끼게 할 것이다.
출처
What the heck is the event loop anyway? | Philip Roberts | JSConf EU
Last updated