본문 바로가기
Developing Note/JavaScript

[JavaScript] 이벤트와 동시성모델

by dev_mac-_- 2018. 12. 2.

JavaScript는 싱글 스레드 기반 언어라는 큰 특징을 가지고있다. 싱글스레드라는 말은 곧, 한가지의 작업만 처리할 수 있다는 말인데 실제 웹 브라우저를 사용하다 보면 동작되는 JavaScript들의 수많은 작업들이 동시에 처리되고 있는 것 처럼 보인다.

싱글 스레드라는데 어떻게 동시에 여러가지 처리하는 것 처럼 느껴질까?

이유를 살펴보기 전에 JavaScript에서 Server Side 언어로 인기있는 Node.js의 Wikipedia 설명을 보았다.

JavaScript기반에 Non-blocking I/O와 단일 스레드 이벤트 루프를 통해 높은 처리 성능을 가지고 있다.

라고 설명이 된다. 그렇다면 단일 스레드 이벤트 루프가 무엇일까?

이때, 자바스크립트에서는 이벤트 루프라는 개념을 사용한다.

 

먼저, 전체적인 Browser안 JavaScript Engine이 어떻게 작동되고 있는지 표현한 그림이다.

왼쪽에 있는 Memory Heap과 Call Stack 부분은 JavaScript Engine이고 Web APIs는 Browser에서 제공하는 것을 사용한다.

 

더 자세히 JavaScript Engine을 살펴보자.

크게 3가지 영역으로 나뉜다. 

Stack, heap, queue

Call Stack

JavaScript의 함수가 실행되는 방식을 보통 "Run to Completion"이라고 표현한다. 하나의 함수가 실행되면, 실행중인 함수의 실행이 수행을 마칠때 까지 다른 작업을 처리할 수 없는 것을 뜻한다. 

Call Stack은 JavaScript Engine은 하나의 Call Stack을 사용하며, 현재 스택에 쌓여있는 모든 함수들이 실행을 마치고 스택에서 제거되기 전까지 다른 어떠한 함수도 실행될 수 없는 것이다.

다음 예제코드를 살펴보자

function foo(b) {
  var a = 10;
  return a + b + 11;
}

function bar(x) {
  var y = 3;
  return foo(x * y);
}

console.log(bar(7));
 

위의 그림은 JavaScript의 Call Stack을 표현한 그림이다. 

console.log를 먼저 스택에 담고 bar함수를 스택에 담는다. (bar 함수안에 있는 local 변수는 bar 스택프레임에 생성된다.)

그 다음 bar을 pop을 하면 foo 함수가 담긴다. 그 이후 foo를 pop한 후 리턴한 값을 console.log함수로 42값을 출력한다.

 

Heap

객체는 힙에 할당이된다. 힙은 대부분 구조화되지 않은 메모리 영역을 나타낸다.

 

Queue

JavaScript Runtime 환경에서는 처리해야하는 메세지들을 임시 저장하는 Queue가 존재한다. 

(Task Queue 혹은 Event Queue로 불린다)

 

다음코드를 살펴보자

setTimeout(() => {
  console.log("첫번째");
}, 0);

console.log("두번째");
 

코드만 보고 결과값을 예상해본다면 setTimeout이 0ms이니 첫번째, 두번째 순으로 콘솔창에 출력될 것 같지만, 사실 두번째, 첫번째로 출력을 하게된다.

그런데 여기서 의문점이 생긴다. 

처음에 JavaScript는 싱글 스레드를 가지고 있어서 한가지 일만 진행할 수 있다고 했다. Task를 처리하는 Call Stack을 보더라도 한 함수가 끝나기 전까지는 다른 함수가 실행될 수가 없다. 그런데 결과값은 두번째, 첫번째 순으로 나온다.

 

Event Loop

서두에 언급한 이벤트 루프라는 개념을 여기서 사용을 하게 된다. 이벤트 루프는 Call Stack이 비워질 때마다 Queue에서 Callback 함수를 꺼내와서 실행하는 역할을 한다. 

그런데, setTimeout과 같은 Web API 영역에 지정되어 있는 비동기 함수는 Call Stack에 쌓이지 않고 앞에서 언급한 Queue에 enqueue되게 된다. 

 

Call Stack을 표현해보면 이런식으로 진행된다. setTimeout함수는 Call Stack에 들어가는 순간 바로 pop된다. 내부에 있는 console.log는 Queue에 enqueue가 된다. 그 다음 console.log("두번째")가 Call Stack에 push된 후 pop이 된다. 이제서야 Queue에 있던 console.log는 그때 Call Stack에 push된후 pop이 되서 결과값이 예상과 다르게 나오게 된다.

 

그렇다면 Event Loop는 어떤식으로 동작하는 건가에 대해서 아래의 코드를 보면 간단하게나마 알 수 있다.

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

 

MDN에서 Event Loop를 위의 코드로 나타낸다. 즉, Event Loop는 현재 실행 중인 Task가 없는지 Queue에 Task가 존재하는지를 반복적으로 확인한다.

만약, Queue안에 처리해야할 Task가 존재한다면 해당 이벤트를 처리한다. 

 

※ Visualize한 애니메이션을 보고 싶다면 아래 사이트를 이용하면 쉽게 이해할 수 있다.

http://latentflip.com/loupe

 

참고

https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoophttps://github.com/nhnent/fe.javascript/wiki/June-13-June-17,-2016

https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf

https://youtu.be/8aGhZQkoFbQ

'Developing Note > JavaScript' 카테고리의 다른 글

[JavaScript]ES6 Object 객체  (2) 2018.10.28
[JavaScript]반복문 for in vs for of  (0) 2018.10.28

댓글