자바스크립트 함수 실행에 대한 이해

자바스크립트는 싱글스레드로 단 1개의 동시성만 다루는 언어이다.

싱글 스레드

한번에 하나의 작업만 수행 할수 있음을 의미함.
다른 작업이 중간에 끼어들수 없고, 기존에 수행하던 작업이 끝나야만 그 다음 작업을 수행할수 있다.

자바스크립트의 동작원리

자바스크립트는 힙, 큐와 함께 구성하는 단일 콜스텍을 갖는데 이것은 V8내부에 구현되어 있다.

https://developer.mozilla.org/ko/docs/Web/JavaScript/EventLoop

그런데 Javascript의 특징들을 보면 비동기, 동시성, 논블로킹(NonBlocking)I/O등의 상반되는 개념들이 등장한다.

싱글 스레드라면서 어떻게 동시성을 가질수 있지?

1. 콜 스택

콜스텍이란?

함수의 호출을 기록하는 자료 구조 이다. 기본적으로 프로그램 안에서 위치한 곳인데, 만약 함수를 실행시킨다면, 스택 위에 무언가를 올리는 행위(push 라고 한다)를 하는 거다. 그리고 함수로 부터 반환을 받을 때, 스택의 맨 위를 가져오는 것(pop 이라고 한다)이다.

Error Stack Trace (credits[https://www.youtube.com/watch?v=8aGhZQkoFbQ])

함수를 재귀적으로 여러번 부르다 무한루프에 빠지게 되는 경우가 있는데 , 크롬 브라우저는 16000프레임의 제한된 스택을 가지고 있어 이 범위를 넘어서게 된다면 Max Stack Error Reached 라는 상태가 되고 실행 중이던 것을 멈춰버린다.

credits[https://www.youtube.com/watch?v=8aGhZQkoFbQ]

2. 힙

객체들은 힙 내부에 할당된다. 힙은 거의 구조화되지 않은 영역의 메모리 이다. 변수와 객체들의 모든 메모리 할당이 여기서 일어나게 된다.

3. 큐

큐는 실행될 콜백함수나 실행될 메세지들에 대한 리스트 이다. 스택이 충분한 공간을 갖고 있을 때 , 메시지는 큐 밖으로 나오게 되고 메시지가 가지고 있던 함수 목록들이 실행된다. 이렇게 초기 스택 프레임이 만들어 진다. 스택이 다시 비어있을때 메시지 수행도 끝난다.
이벤트 들에 대한 콜백 함수가 제공되었다고 했을때 이 메시지들은 외부 비동기 이벤트(마우스 클릭, HTTP요청 등을 말한다)들에 대한 응답으로 큐에 쌓인다. 하지만 만일 사용자가 버튼을 눌렀는데 아무런 콜백함수도 등록되어 있지 않다면 어떠한 메시지도 큐에 들어가지 않을것이다.

4. 이벤트 루프

일반적으로 자바스크립트 코드의 성능을 측정할 때, 스택 안에 있는 함수는 성능을 느리게도 빠르게도 만든다. 만약 console.log()한줄만 있다면 코드의 실행은 빠르게 될것이다. 하지만 코드가 길어진다면 느려지게 될것이고, 그 코드들은 스택을 계속 차지하고 있을 것이다. 이러한 상황을 'Blocking Script' 라 부른다.

네트워크 요청, 이미지 요청은 느릴수가 있다. 하지만 서버 요청들은 비동기 함수인 AJAX를 통해 할수 있다. 만일 이러한 네트워크 요청들이 동기화 함수로 이뤄 졌다고 하면 어떻게 될까? 먼저 컴퓨터가 네트워크 요청을 받는 다면 그 요청은 또 다른 컴퓨터나 기계와 같은 어떤 서버로 갈것이다. 네트워크 응답은 응답자의 사정에 따라 느려지게 될수 있다. 그동안 CTA(Call To Action) 버튼이나 렌더링이 필요한 무언가를 클릭한다면 스택이 막혀있어 어떠한 반응도 일어나지 않을것이다. 싱글스레드 언어인 자바스크립트는 스택에 쌓인 함수들에서 어떠한 값을 반환하기 전까지는 불가능한데..
그 웹페이지는 브라우저가 아무것도 할 수 없기 때문에 완전히 망가질 것이다. 우리가 엔드 유저를 위해 유동적인 UI를 원한다면 이러한 방법은 그리 이상적이지 않는다. 그렇다면 어떻게 해결해야 할까?

자바스크립는 비동기 콜백들을 제외하고 한번에 한가지 일만한다.

가장 쉬운 해결책은 비동기 함수들을 사용하는 것이다. 즉 우리가 코드의 일정 부분을 실행시키고 나중에 실행될 콜백함수를 스택에 넣는 것을 말한다.

비동기형 콜백 함수의 종류

$.get(), setTimeout(), setInterval(), Promises, AJAX

자바스크립트가 싱글 스레드 언어임에도 불구하고 웹사이트에 끊김없이 여러 작업을 동시에 할수 있는 것은 바로 브라우저가 Web APIs 같은 것들을 제공하여 비동기 작업을 가능하게 해주기 때문이다.

노드는 비동기 함수 실행이 전부 인데, 모든 비동기 콜백들console.log(), mathematical operations은 코드에서 읽히자 마자 바로 실행되지 않고 잠시 후에 실행된다. 그래서 동기 함수들과는 다르게 바로 스택의 내부로 push될 수 없다. 그렇다면 비동기 콜백은 대체 어디로 가고 어떻게 다뤄질까?

위의 코드에서 자바스크립트의 네트워크 액션 요청을 보면

  1. 요청 함수가 실행된다. 요청이 들어온 때에 실행될 콜백으로 onreadystatechange 이벤트 안에 있는 익명의 함수를 넘긴다..
  2. "Script call done!"은 동기 함수로 코딩되어 있기 때문에 바로 콘솔의 output에 들어갑니다.
  3. 비동기 함수가 실행될 때, 서버로부터의 응답이 오고 body부분을 콘솔에 출력하며 콜백이 실행된다.

respose에서의 호출자(caller)의 분리는 자바스크립트 런타임이 당신의 비동기 명령이 완료되고 콜백이 호출될 때까지 기다리는 동안 다른일을 하는 것을 허용한다. 2번에서 브라우저 API들이 작동된다. DOM이벤트들, http요청들, setTimeout과 같은 비동기 이벤트들을 다루기 위해 브라우저 내부 C++로 구현된 코드들에 의해 만들어진 기본적인 스레드들(threads)의 API를 호출한다.

DOM events, http request, setTimeout과 같은 비동기 이벤트들을 다루기 위한 브라우저의 웹 API 스레드들은 브라우저 내부에 C++로 구현되어 만들어졌다.

지금 이 웹 API들은 스스로 자신들의 실행코드를 스택에 넣을수 없다. 만약 이런일이 일어 난다면 코드 중간에 랜덤하게 나타나게 된다. 위에서 다뤄진 메세지 콜백 큐가 이것을 증명하는 데, 3번의 코드가 끝난다면 웹 API중 어느 하나다 콜백을 큐에 얹는다. 이벤트 루프는 큐 안의 콜백들을 스택이 비었을 때 밀어 넣는 일을 담당 한다. 이벤트 루프가 하는 기본적인 일 중에 하나는 스택과 작업큐를 보고 스택이 비었을 때 큐에 첫번째에 있는 콜백을 스택에 넣는다. 다른 메시지가 들어오기 전에 각각의 메시지 또는 콜백들은 작업을 완료한다.


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

메세지를 계속 기다리다가 다음 메세지를 진행하는 역할을 한다.

Javascript Event Loop

메시지들은 웹 브라우저에서 언제든 이벤트가 발생했을때 추가된다. 그리고 이벤트들에는 이벤트 리스너가 붙어 있다. 만일 리스터가 없다면 발생한 이벤트는 그냥 사라지게된다. 언제든 우리가 웹 브라우저에서 어떤 요소를 클릭했을 때 , 클릭 이벤트 핸들러는 큐에 메시지를 추가하게 된다. 웹 브라우저의 다른 이벤트들도 동일하다. 이러한 콜백 함수 호출은 콜스택 안에서 초기의 프레임의 역할을 하게된다. 그리고 자바스크립트는 싱글 스레드이기 때문에, 추가적인 폴링 중 메시지와 포로세싱은 잠시 중단되고 스택에 있는 모든 호출들의 return을 기다린다. 그리고 동기 함수들은 스택에 새로운 콜 프레임들을 추가한다.


참고

profile
어제보다는 오늘 더 나은

0개의 댓글