[JS] 동기와 비동기에 대해 알아보자

Pakxe·2023년 1월 8일
2

JavaScript

목록 보기
12/16
post-thumbnail

이 글은 동기 비동기의 개념과 비동기가 어떻게 동작되는지를 많이 축약해서 설명합니다.
(구체적인 동작 과정은 나중에 추가하겠습니다.)

일단 이 단어 자체부터 이해해보자.

단어의 뜻, 유래를 살펴보면 개념의 이해를 도와주기도 한다.
우리가 알아가려는 단어는 왜 동기, 비동기라고 이름붙여진걸까?

비동기는 동기의 반대라 비동기라는 이름을 가진 것일 테니까, 동기를 제대로 알면 비동기도 자연스레 알게될 것 같다!

그런데 이 단어는 한국에서 탄생한 단어가 아니다. Synchronous(=동기의 영어 번역)라는 단어가 한국에서는 동기라고 번역되었을 뿐이고 Synchronous동기가 정확하게 뜻이 일치하지 않는다.

단어에서 개념의 힌트를 얻는 것에 실패했다. 그러니 우리가 사용해야하는 상황인 프론트 엔드에서의 동기란 무엇일까를 찾아봐야 한다.

프론트 엔드에서의 동기

‘나는 멀티가 안되는 사람이야.’

살면서 한번은 들어본 말이다. 이런 말을 한 사람은 음악을 들으면서 공부도 하면서 수다까지 동시에 할 수 있을까? 불가능하다. 동시에 하나에만 집중할 수 있는 사람이니까 멀티가 안되는 사람이라고 자신을 소개했을 것이다.

저 사람을 프론트 엔드에서의 동기로 설명하면 동기적인 사람이라고 할 수 있다. 하이라이트된 동시 에 집중하며 더 읽어보자.

그러면 비동기는 무엇일까. 우리는 보통 코딩할 때 노래를 틀어놓고 하는 경우가 많다.(물론 집중하면 노래가 안들리겠지만.. 넘어가주세요)

그럼 우린 코딩과 음악을 동시 에 수행하고 있는 것이다.

우리는 프론트 엔드의 동기로 설명하면 비동기적인 사람이라고 할 수 있다.

이제 감이 잡혔으면 좋겠다. 사실 동기라는 단어는 일상 생활에서 자주 사용하지 않기도하고, 이 동기라는 단어 자체의 의미가 프론트엔드에서의 동기와 가깝지 않다. 그래서 나는 동기→ 동시라는 개념으로 바꿔서 생각해왔다.

  • 동기: 비동시(한번에 하나만 할 수 있는 사람)
  • 비동기: 동시(멀티가 되는 사람)

사실 이미지로 이해하면 더 간단하다.

주어진 일을 차례차례 수행하는 것이 동기이며, 동시에 여러 일을 수행하는 것이 비동기인 것이다! 앞서 하던 일이 안끝나도 다른 일을 한다. 성실하다.. 그런데 이 동시라는 예시는 사실 앞으로 설명할 동기 비동기를 이해하기엔 살짝 적절치 않다. 하지만 저 이미지를 이해하기엔 충분할 거고, 자바스크립트를 잘 모르는 초보 개발자에게 정말 쉽게 설명해줄 수 있게됐다.

우리는 초보가 아니어야 하니까.. 여기서 한 가지를 더해 동기 비동기를 이해하면 좋다. 저 이미지의 왼쪽인 동기 부분을 잘 보면 알겠지만, 1번 작업이 끝나고 나서 바로 2번 작업을 실행한다. 그리고 2번 작업이 끝나면 바로 3번 작업을 실행한다. 이것으로 보아 동기란 순서가 있고 이 순서를 지켜가며 작업을 진행한다는 걸 알 수 있다. 덧붙이자면 현재 작업의 종료(응답)과 동시에 다음 작업의 시작(요청)인 것이다.

그렇다면 업그레이드된 동기의 개념의 반대인 비동기는 뭐라고 이해해야 할까?
동기는 순서가 있다고 했으니, 비동기는 순서가 없다고 하면 된다. 현재 작업의 종료와 동시에 다음 작업의 시작이 아닌, 현재 작업의 종료와 다음 작업의 시작은 같은 순간에 일어나지 않는다. 자기들 맘대로 1번일 하다가 3번일도 시작해버릴 수 있다는 말이다.

그런데 자바스크립트는 싱글스레드라며..

싱글스레드: cpu코어가 하나인 싱글코어를 생각하면 된다. 일을 처리하는 곳이 하나뿐인 것
그래도 이해가 안된다면…

팥빵, 크림빵 두 가지를 만들 수 있는 공장이 있다고 해보자. 그런데 이 공장은 한번 가동하면 하나의 빵 종류만 만들 수 있다. 팥빵, 크림빵을 동시에 이 공장에선 만들 수 없다. 그러면 팥빵, 크림빵을 둘 다 만들어달라고 요청이 들어오면 어떻게 해야할까?

두 가지 방법이 있다.
첫번째는 이 공장에서 팥빵을 다 만든 후에 크림빵을 만들면 된다. 이 방식이 싱글스레드이다.
두번째는 이 공장과 똑같은 공장을 하나 더 짓는다. 그리고 원래 공장에서는 팥빵을, 새로만든 공장에서는 크림빵을 만든다. 동시에 두 가지 빵이 만들어진다. 이 방식이 멀티스레드이다.

앞에서 우리는 동기와 비동기에 대해 동시 라는 말로 빗대어 이해했다.

그런데 자바스크립트는 싱글스레드다. 갑자기 이 말이 왜나오냐면, 싱글스레드는 동시에 여러가지 일을 할 수 없다. 그런데 우리는 vsc에서 동시 작업인 비동기를 잘만 쓰고있다. 이게 도대체 어떻게 가능한 것일까?

node.js까지 알아야 한다

우리가 동아리에 들어와서 처음 깔았던 node.js… 이게 비동기를 가능하게 해준다. 어떻게 가능하게 해주는걸까? 얘가 뭐길래..

node.js 의 공식 사이트에 들어가보면 대문짝만하게 써있는 문구가 있다.

단어 하나하나가 어렵다. chrome V8은 또 뭐고 javascript 런타임은 도대체 무슨 말일까?

chrome V8

사진의 chrome V8 바로 옆에 JavaScript 엔진이라고 써있다. 엔진이란 차를 굴러가게 해주는 장치다. 자바스크립트 엔진이라면 자바스크립트를 굴러가게(실행하게) 해주는 도구일 것이다.

그러면 chrome V8는 자바스크립트를 굴러가게 해주는 도구의 이름이겠다. 이름을 한눈에 보고 알기 쉽게 지어주면 좋았을 텐데…

이 V8로 크롬에서 자바스크립트 파일(.js)을 굴러가게 할 수 있는 것이다.

추가로…

크롬 말고도 브라우저는 많다. 파이어 폭스도 있고 엣지, 사파리도 있다. 이 브라우저들도 자바스크립트를 실행할 수 있도록 자기들만의 자바스크립트 엔진을 갖고 있다

JavaScript 런타임

런타임이란 프로그래밍 언어가 구동되는 환경이다. 자바스크립트 런타임이라면 자바스크립트가 구동되는 환경이다. 말이 어렵다. 예를 들어 이해해보자.

햄스터를 키우고 싶어졌다. 햄스터는 어떻게 키워야할까? 일단 집도 만들어줘야하고, 톱밥도 깔아줘야한다. 그리고 챗바퀴도 놔주고 밥도 물도 안에 들여놔야 한다. 그러면 비로소 햄스터가 살 수 있는 환경 이 된다.

자바스크립트도 똑같다. 자바스크립트 엔진 자체만으로는 뭘 못한다. 자바스크립트가 잘 동작하기 위한 환경 을 만들어줘야한다.

자바스크립트를 잘 굴릴려면, 자바스크립트가 비동기로 동작하기 위한 장치도 있어야 한다. 그 외에도 여러가지가 필요하다.

둘 다 읽어봤다면 다시 Node.js의 대문짝 이미지를 봐보자. 이제 이해가 조금은 될 것이다.

아직 어렵다면.. 사진의 말을 이렇게 이해하면 된다.

Node.js는 크롬에서 만든 자바스크립트를 굴러가게 해주는 장치가 포함된 자바스크립트 실행 환경이다.

여기서 주목할건 포함 이다. 그럼 저 V8엔진 말고도 다른게 Node.js 안에 더 있나보다. 그리고 사실 자바스크립트 엔진이 자바스크립트 자체라고 생각하면 엔진은 싱글스레드인게 된다. 그럼 비동기를 어떻게 하는지 아직 해결이 안됐다.

여기까지 잘 이해했다면 Node.js안의 다른 장치가 자바스크립트의 비동기를 대신 수행해준다는걸 예상할 수 있을 것이다.

자바스크립트 런타임(실행 환경)은 아래 사진처럼 생겼다.

(많은 것이 생략된 이미지입니다.)

붉은 부분인 자바스크립트 엔진외에 파란색으로 Libuv라는 장치가 있다. 이 Libuv가 싱글스레드라 슬픈 자바스크립트 엔진 대신 비동기를 대신 수행해준다.

(자바스크립트 엔진의 메모리 힙, 콜스택은 실행 컨텍스트에서 설명될 내용인 것 같으므로 생략했습니다.)

이제 비동기가 어떤 과정으로 수행되는지 알아보자

(사실 동기는 그냥 우리가 평소 사용하던 c++생각하면 더 쉽다. 적힌 순서대로 정직하게 수행하는 이해하기 쉬운 방식이다. 그래서 동기가 어떻게 수행되는지는 생략했다.)

자바스크립트 코드에서 비동기로 호출되는 함수들은(axios로 사용했던 것들) 자바스크립트 엔진이 처리하지 않는다. libuv의 task queue로 보내진다.

이 과정은 직접 코드를 실행하면서 알아보자.

일단 비교를 위해 동기 코드를 실행해보자.


(동기 코드 예시)

이 코드의 실행 결과는 모두가 예상할 수 있다 시피.. 1 2 3 이 그대로 출력된다. 1번→2번→3번 줄로 순서를 지켜가며 코드가 실행됐다.

이제 비동기 코드를 실행해보자. 비동기는 setTimeout이라는 함수를 사용해서 구현한다.

setTimeout 함수는 비동기 함수이다. 이 함수는 일정 시간 후에 인자로 전달된 코드를 실행한다.

첫번째 인자로는 실행할 코드, 두번째 인자로는 얼마 후에 실행할 건지 시간을 ms 단위로 적는다. 1000이 전달됐다면 1000ms = 1s 이므로 1초 뒤에 첫번째 인자로 전달된 코드를 실행하겠다는 의미다.


(비동기 코드 예시1)

위 코드의 실행 결과는 어떻게 될까? setTimeout함수에 2를 출력하는 코드와 3초를 인자로 줬다. 이 뜻은 3초 뒤에 콘솔에 2를 출력하겠다는 것이다. 그러니 콘솔에는 1 3 2가 출력될 것이다.


(결과)

예상대로 1 3 2가 출력됐다. 그러면 이번엔 setTimeout 함수가 0초뒤에 실행되도록 해보자. 그러면 동기코드때 처럼 1 2 3이 출력될 것이다.


(비동기 코드 예시2)


(결과)

예상한 결과가 아니다. 1을 출력하고 0초뒤에 2를 출력할테니 순서대로 1 2 3이 콘솔에 찍힐 것이라고 예상했는데 아니다. 왜 이런 결과가 나온걸까? 그리고 굳이 왜 이런 예제를 사용한 걸까?

내부 구조

정말 간단하게 얘기하자면 자바스크립트는 비동기 코드를 만나면 자바스크립트 엔진이 아닌 다른 곳으로 보내둔다.(web API라는 곳인데 이건 따로 추가할게요) 그리고 이 js 파일 내의 ‘모든’ 동기 코드가 전부 실행이 완료된 상태라면 그제서야 다른 곳에 맡겨놨던 비동기 코드를 데려와 실행한다.

즉, 동기 코드가 다 끝난 후에 비동기 코드를 실행하는 것이다. 그런데 이건 사실 싱글 스레드랑 똑같다. 그럼 그냥 자바스크립트 엔진 안에 비동기 코드를 저장할 공간을 두고, 동기 코드 다 실행하고 비동기 코드 실행하면 된다. 비동기를 맡아줄 장치따위는 필요없는 것이다. 근데 왜 굳이 node.js는 주렁주렁 뭔갈 갖고 있는 걸까?

이건 자바스크립트 엔진이 코드를 어떻게 실행하는 지를 알면 이해할 수 있다. V8과 같은 자바스크립트 엔진은 call stack이라는 구조에 할일을 담아 수행한다. 제일 위에있는 할일부터 해치운다. 그렇게 할일을 하다가 콜스택이 비어버리면 그때 비동기 코드가 콜스택에 쌓여 다시 자바스크립트는 자신이 하던대로 스택이 비도록 작업을 하나씩 수행하는 것이다.(스택은 다들 아실거라 생각해서 설명 생략합니다.)

  • call stack? 콜스택을 예를 들어 이해한다면.. 공부를 하다가 시켰던 음식이 배달왔다. 그럼 하던 공부를 계속할까 밥을 먹을까? 대부분 밥을 먹을 것이다. 그런데 밥을 먹다가 중요한 사람한테 전화가 온다. 그럼 밥을 계속 먹을 것인가? 전화를 받아야 할 것이다. 전화로 용무가 다 끝났다면 전화를 끊는다. 그리고 먹다가 멈췄던 밥을 먹는다. 밥을 다 먹었다면 이제 아까 하고있던 공부를 하러간다. 이게 콜스택이다. 일을 하다가 다른 일을 해야한다고 하면 하던일을 끝내고 새로 받은 일을 하는게 아니라. 하던 일을 멈추고 새로 받은 일을 홀라당 하러간다. 스택의 특징을 잘 알고있다면 머릿속으로 그리면서 콜스택을 아주 잘 이해할 수 있다. FILO이니까..

그런데 자바스크립트 엔진은 정말 바보다. 주변을 보지 못한다. 이게 무슨말이냐면…

자신의 call stack만 본다. 그 외 주변은 보지 못한다. 동기적으로 작업할 일이 콜스택에 착착 쌓여있으니 자바스크립트는 그걸 하나씩 수행한다. 그러다 비동기 코드를 만나버리면.. 지금 할 수 없는 일이니 다른 곳으로 던져버리는 것이다. 만약 비동기 코드가 콜스택에 남아있다면 자바스크립트는 그걸 할 일이라 인식하고 수행해버릴테니 비동기가 아니게 되어버린다. 그래서 비동기를 잠시 맡아주고 콜스택이 비면 그걸 인식해 맡아뒀던 비동기 코드를 콜스택에 집어 넣어주는 장치가 있는 것이다.

요약

우리는 이제 앞에서 이해한 비동기 개념인 순서대로 실행하지 않는, 현재 작업의 종료와 다음 작업의 시작이 바로 연결되지 않는 이 말이 잘 적용되는걸 알았다.

비동기 코드가 있다면 코드가 적힌 순서대로 실행되지 않는다. 비동기만 딴데 보내놨다가 다른걸 실행한다. 현재 작업의 종료와 다음 작업의 시작이 바로 연결되지 않는것이다.

이제 우리는 동기 비동기에 대해 조금 이해하게 됐다. 짝짝

그래서 왜 비동기를 쓰지?

그냥 코드 읽는데 이해도 잘되고, 우리가 의도했던 순서대로 잘 실행되는데 동기만 쓰지 왜 비동기라는 걸 만들어서 이렇게 힘들게 하지?

이건 사용자의 입장에서 인터넷을 잘 생각해보면 왜 비동기를 만들었는지 알 수 있다.
유튜브같은 고용량 이미지, 비디오가 많은 웹사이트는 이 파일들을 가져오는데에도 많은 시간이 걸린다. 간단하게 카카오톡 사진만 하더라도 30장 보내는데 그렇게 오래걸리는데, 유튜브도 마찬가지일 것이다.

그런데 이 유튜브는 이미지를 서버에서 받아오는 중에도 화면을 표시한다. 이런 비슷한 로딩 화면을 앱이나 웹에서 많이 봤을 것이다.


(유튜브 로딩중..)

유튜브는 미리 이런 로딩 화면을 먼저 사용자에게 띄워주고, 우리가 이미지를 받아오는 중이다 라는걸 알려준다. 그리고 짧은 시간 후에 우리가 평소에 보던 썸네일 가득한 유튜브 화면이 보여진다.

이를 위해 비동기가 있는 것이다. 만약 모든 코드가 동기로 동작했다면, 우리가 쓰는 리액트는 일단 컴포넌트를 만들고 이미지 받아올 것들 다 받아오는데에 한 세월 걸리고 그것들 자식 컴포넌트에 전달해주고 그렇게 만든 virtualDOM(리스트에 이미 있어서 설명 안하겠습니다.)을 브라우저의 DOM tree에 업데이트까지 해야 드디어 완성된 화면을 보여주게 될 것이다.

과정이 많이 생략됐지만 일단 저 일련의 과정들을 다 거치고 나서야 유튜브가 보인다는 것이다. 그런데 사용자 입장에서는 웹브라우저에 아무것도 없는 화면을 반가워할까? 아니다.

일단 틀이라도 보여주며 우리가 이미지를 열심히 받아오는 중이라고 알려주면 사용자 경험에 있어서 아무것도 없는 맨들맨들 화면보다는 훨신 나을 것이다. 그래서 동기적으로 일단 틀을 다 찍어내고 화면에 띄운 후에 고용량 이미지들을 비동기로 받아와 나중에라도 보여준다.

그 외에도 비동기를 사용하는 다른 예가 있는데 더 공부해보도록 하겠다!


추가로 공부하면 좋을 개념

  • 블로킹 논블로킹
  • async/await
  • Promise
  • 실행 컨텍스트
  • web API
  • 이벤트 루프
  • 비동기의 구체적인 내부 동작 과정

참고한 자료

https://evan-moon.github.io/2019/09/19/sync-async-blocking-non-blocking/

https://ingg.dev/js-work/

profile
내가 꿈을 이루면 나는 또 누군가의 꿈이 된다.

0개의 댓글