진짜 개발자
본문 바로가기

Web/javascript

javascript - 동기, 비동기 처리과정과 event loop

728x90
JavaScript 비동기함수의 이해

Philip Roberts의 What the heck is the event loop anyway? 발표 영상을 보고 정리하는 포스팅입니다. 다음의 링크로 이동하시면 보실 수 있습니다. https://www.youtube.com/watch?v=8aGhZQkoFbQ


 

그림출처 - PhilipRoberts

JavaScript의 동작 과정의 최종그림입니다. 천천히 하나하나 알아보도록 하겠습니다.

 

1. 동기

1.1. 싱글스레드?

다들 잘 아시다시피, JavaScript는 싱글스레드 프로그래밍언어로 하나의 싱글스레드로 동작을 합니다. 즉, 한순간에 하나의 로직만을 처리해 나아갈 수 있다는 의미 입니다.

 

1.2. Call Stack

이제 맨 처음 본 그림에서 CallStack에 대해 알아보겠습니다. CallStack에서 하는일은 현재 호출된 함수를 차근차근 CallStack에 쌓고 다 실행된 함수를 CallStack의 가장 위에서 제거하는 역할만을 합니다.

 

싱글스레드의 동작 과정을 간단히 알아보겠습니다. 위와 같은 javascript를 실행해본다고 가정합니다.

맨처음 실행되는 코드 자체를 의미하는 main()이 CallStack에 쌓입니다.

 

그후 b가 호출되어 b함수가 CallStack에 쌓입니다.

 

다시 b함수가 c함수를 호출하여 c함수가 CallStack에 쌓입니다.

 

이제 c()함수가 console.log('c');를 실행하고 종료되어, CallStack에서 제거됩니다.

 

c함수가 종료되므로써 b의 코드도 모두 끝이나기때문에, b함수 역시 CallStack에서 제거됩니다. 모든 코드가 실행이 완료되었으므로 main()역시 CallStack에서 제거되며, 실행이 완료됩니다.

 

 

1.3. Blocking

blocking이란 엄청 느리게 동작하는 코드를 의미합니다. console.log() 등의 코드는 Blocking이 아닙니다. 정말 눈깜짝할 새 끝나는 코드이기 때문이죠.

 

네트워크 요청, 이미지 프로세싱 등의 요청은 상대적으로 굉장히 오랜시간이 걸리는 동작입니다. 이렇게 느린 동작의 코드들이 CallStack에 남아있어서 다른 코드들의 실행을 방해하는것을 바로 Blocking이라고 합니다.

 

만약 동기적으로 호출되는 함수 getNaver()라는 함수가 있다고 가정해보겠습니다.

 

위의 코드를 실행하면 다음과 같은 현상이 발생합니다. 브라우저는 계속해서 호출중이며, Console에는 아무런 값도 나타나지 않고 button은 Click할 수 없는 상태가 됩니다.

 

그리고는 while문이 종료되면, console에 값이 찍히면서, alert가 나타나게 됩니다. 즉 동기적으로 실행 되기 때문에 먼저 CallStack에 들어간 getNaver()함수가 종료되기 전까지 다른 코드들을 blocking하고 있게되고, 그 때문에 다른 코드들을 실행할 수 없었던 것입니다.

 

 

2. 비동기

위와 같은 현상을 해결하기 위한 해법으로 비동기가 있습니다. 비동기란 어떤 오래걸리는 작업의 코드를 실행하면 후에 결과로 CallBack을 받고 그 코드에대한 처리는 나중에하고 다른 코드를 실행한다는 것 입니다.

 

위와 같은 코드가 있습니다. 먼저 실행하고 결과를 보며 설명드리겠습니다.

 

우선 hi, bye가 먼저 콘솔에 찍히고,

5초가 지난뒤 done이 찍히는 것을 볼 수 있습니다.

 

CallStack을 살펴보며 어떻게 된 일인지 알아 봅시다. 가장 처음 당연히 main()에 스택에 쌓입니다.

 

console.log('hi')가 실행되기 위해 스택에 쌓입니다.

 

실행이 종료되며 console에 hi를 찍고, 스택에서 제거됩니다.

 

setTimeout()의 경우 조금 특별하게 동작합니다. 우선 실행되기 위해 stack에 쌓입니다.

 

그러고는 갑자기 사라집니다. 우선은 그냥 지나가겠습니다.

 

그 후 console.log('bye')가 실행되고, console에 'bye'를 찍고 스택에서 제거됩니다.

 

이후 모든 코드의 실행이 완료되었으므로 main도 스택에서 제거됩니다.

 

그러고는 갑자기 console.log('done')이 스택에 등장합니다. 이것이 어떻게 가능할까요? javascript는 싱글스레드 프로그래밍 언어로써, 한순간에 하나의 코드만이 처리가 가능한데 while에서 5초간 기다린 코드를 어디서 처리했을까요?

 

 

2.1 EventLoop 와 동시성

여기서 EventLoop가 등장합니다. 말씀드렸듯이 Javascript는 싱글스레드 프로그래밍언어입니다. 따라서 한순간에 두개의 코드를 실행할 수 없습니다. 다시 맨처음의 그림을 보시면 WebAPIs라는 것이 보일겁니다. 여기서 JavaScript에서 호출할 수 있는 스레드를 지원합니다. 여기에서 동시성이 해결되는 것입니다.

 

그림을 보며 설명드리겠습니다. 다시 setTimeout(cb)를 호출하는 시점입니다. setTimeout()은 사실 브라우저에서 제공하는 API입니다. 따라서 이 호출을 webapis에게 넘기게 됩니다.

 

이렇게 함으로써 setTimeout()함수의 호출은 끝났으므로, CallStack에서 제거가 될수 있고, webapis에서는 timer를 실행시킵니다.

 

이 후 console.log('bye')가 실행이 모두 끝나고, CallStack은 빈상태가 됩니다.

 

그 후 timer가 종료되고 CallBacktask queue에 집어넣습니다.

 

여기서 Event loop가 동작을 합니다. event loop는 loop를 돌며 CallStack과 task queue를 주시합니다. task queue에 CallBack이 있고 CallStack이 비어있다면 task queue의 CallBack을 꺼내서CallStack에 넣어줍니다.

 

마지막으로 cb 을 실행하게 됩니다.

 

 

 

3. setTimeout(cb, 0);

setTimeout(cb, 5000)코드를 setTimeout(cb, 0); 로 바꾸면 어떻게 될까요? 앞서서 event loop는 task queue에 작업이 있다고 한들, CallStack이 비어있지 않으면 CallBack을 CallStack에 넣지 않는다고 했습니다. 따라서 setTimeout(cb, 0); 역시 javascript의 모든 CallStack이 제거된 후 마지막에 호출이 됩니다.

 

 

 

4. 요약

요약하자면 javascript는 싱글 스레드프로그래밍 언어이기 때문에, blocking이 일어날 만한 정도의 코드를 webapi에서 스레드를 통해 지원하고 있습니다. 따라서 webapi쪽에 해당 작업을 요청하고 javascript는 자신의 CallStack에 있는 함수들을 처리해 나아갑니다.

모든 webapis는 작동이 완료되면 CallBack을 Taskqueue에 집어 넣도록 되어있습니다.

eventloop는 loop를 돌며 CallStack과 task queue를 주시합니다. task queue에 CallBack이 있고 CallStack이 비어있다면 task queue의 CallBack을 꺼내서CallStack에 넣어줍니다. 

따라서 setTimeout(cb, time); 에 time을 아무리 적게 주더라도, CallStack의 모든 코드가 처리된 후에야 CallBack을 실행하기 때문에, 최소한의 대기시간은 정해줄 수 있지만, 그 시간동안만 대기한다는 것은 보장하지 못한다는 것 입니다.