본문 바로가기

개발/Node.js

[Node.js] await은 정말 Non-Blocking일까?

최근 개인적으로 바쁜 일도 끝나고 해서 퇴근 후 스터디를 할까 고민하던 중 Node.js 스터디를 하기로 결심했다.

 

마침 지인 분께서도 함께 하고 싶다는 말씀을 해주셔서 페이스북 그룹을 통해 스터디원을 모집했고, 저번 주부터 스터디를 시작했다.

 

그리고 스터디 첫 날을 위해 책을 읽던 중 과연 await은 Non-Blocking일까? 라는 궁금증이 들었다.

 

 

Non-Blocking과 Asynchronous

 

Node.js가 싱글 스레드 기반의 Non-Blocking/Asynchronous 라는 것은 Node.js를 사용해 본 사람이라면 누구나 들어봤을 것이다.

 

하지만 실제로 Node.js가 구동되는 방식을 보면 Node.js는 과연 싱글 스레드가 맞는가? 라는 의문도 든다.

 

실제로 스터디를 진행하면서 한 분이 해당 주제를 가지고 설명해주셨고, Node.js는 스레디 풀을 이용하고 있기 때문에 엄밀히 말하면 싱글 스레드로 동작하는 것이 아니라는 것을 알 수 있었다. (이 부분은 추후 예제 코드를 통해 실제로 Node.js에서 스레드가 동작하는 방식을 작성할 예정이다.)

 

그리고 Node.js는 Javascript Core Engine과 V8과 libuv 사이를 바인딩해주는 인터페이스로 볼 수도 있다는 것도 새롭게 알게된 내용이였다.

 

또한 기존의 Non-Blocking이 가지고 있던 한계를 극복하기 위해 운영체제가 지원하는 디멀티플렉서를 이용해 이벤트 디멀티플렉싱을 구현하고, 이벤트 디멀티플렉서와 이벤트 큐, 이벤트 루프를 이용해 Reactor 패턴을 사용하고 있는 것도 흥미로웠다. (스터디를 함께 하시는 분이 사실 Reactor 패턴과 Observer 패턴이 다를게 없다고 하셨는데 이 말에 동의하기 때문에 두 패턴에 대해서도 공부한 후 블로그에 포스팅 할 예정이다. 즐겁다 :D )

 

이후 당연하게도 Node.js의 필수 패턴인 콜백의 개념을 다시 찾아보게 되었고, async/await에 대한 소개가 나왔을 때 해당 포스팅의 제목에 대한 궁금증이 생겼다.

 

 

async/await

 

Node.js의 async/await의 동작 방식은 await이 실행되는 시점에 async 내부의 실행을 멈춘다고 공식 문서에서 설명하고 있다.

 

async/await는 분명히 코드를 동기적 보여지기 위해 사용하며, Non-Blocking/Asynchronous 이라고 알고 있었는데 await이 실행되는 시점에 async 내부의 다음 코드들의 실행을 멈춘다는 것은 당연히 Blocking이 아닌가? 라는 생각이 들었다.

 

Node.js는 핸들러라는 개념을 통해 Callback 함수를 표현한다. 그리고 async/await은 반드시 Promise를 반환받아야 한다.

 

따라서 async/await이 당연히 비동기로 동작한다는 것은 당연해보인다.

 

하지만 나는 await의 동작은 엄연히 Blocking이 아닌가? 라는 의문이 들었다.

 

그래서 해당 주제를 스터디에서 논의했지만 구글에서도 글을 쓰는 사람들 마다의 의견이 다분했기 때문에 쉽게 결론이 나지 않았다.

 

그리고 스터디가 끝난 후 집에 가면서 여러 자료들을 찾아봤고, 불과 몇분 전까지만 해도 await이 실행되는 시점은 Blocking이라고 생각했던 내 생각은 바뀌게 되었다.

 

async/await은 반드시 Promise 객체를 반환 받아야 하기 때문에 await은 Promise의 반환값을 기다리는 함수라고 생각할 수 있다.

 

그리고 async 내부의 await이 실행되는 시점에는 해당 함수의 Promise 반환값을 받을 때 까지 async의 내부 코드들의 실행을 멈춘다.

 

하지만 실제로 await은 async 내부 코드들의 실행을 멈추지 않는다.

 

다만 보이지 않는 .then을 통해 다음 코드들을 담아둔다.

 

await의 실행 방식은 함수를 콜백 또는 프로미스 체인으로 작성하는 것과 같다고 보면 된다.

 

즉, async/await은 코드를 동기적으로 실행하는 것처럼 보이지만 실제로 인터프리터의 실행을 멈추는 것은 아니며, await 또한 콜백으로 동작하는 것과 같기 때문에 여전히 다른 이벤트가 이벤트 핸들러를 실행할 수 있는 상태인 것이다.

 

이 말은 곧 다른 이벤트들에 대해 이벤트 큐가 여전히 동작하고 있다는 것이고, await이 실행되는 순간 정말로 async 내부의 실행이 Blocking 되는 것이 아니라 일단 해결되지 않은 Promise를 반환한 후 다른 것을 실행하다가 Promise 객체의 반환값을 받는 시점에 다음 코드를 실행할 수 있는 이벤트가 되는 것이다.

 

 

마치며

 

단순히 await이 Blocking일까? 라는 궁금증을 느끼고, 해결해나가는 과정을 통해 필자가 지금까지 얼마나 Node.js를 모르고 있었는지 깨달을 수 있었다.

 

물론 여전히 모르는 것 투성이고, 심지어 해당 글에 정확하지 않은 설명이 있을 수 있다.

 

그래서 지금까지 알고 있다고 생각했던 것들에 대해 의심할 수 있는 기회가 되었고, 책을 통해 공부하면서 디멀티플렉싱과 리액터 패턴, 그리고 Promise와 async/await 등의 개념을 하나씩 정리할 예정이다.

 

특히 Node.js 디자인 패턴이라는 책은 내용이 정말 좋기 때문에 읽어나갈 수록 Node.js에 대한 깊은 이해를 할 수 있다는 기대감이 생겼다.

 

Node.js 입문 이후에 깊은 이해와 공부를 해보고 싶은 사람들에게 해당 책을 추천하며, "Await은 정말 Non-Blocking일까?"를 마친다.

 


 

 

 

안녕하세요. 평범한 대학생 개발자 yorr입니다.

포스팅을 읽고 궁금한 점 또는 문의가 있으신 분은 메일 또는 댓글을 남겨주세요.

 

Mail: twysg@likelion.org

Github: https://github.com/sangyeol-kim