ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 자바스크립트에서 비동기 처리는 어떻게 이루어 질까? (+자바스크립트 동작 원리)
    JavaScript 2024. 4. 21. 16:24
    글을 쓰게 된 이유는 자바스크립트는 Single Thread로 동기적으로 동작하지만, 자바스크립트로 만들어진 웹 서비스들은 여러 작업들이 비동기적이고 동시에 동작하고 있는데서 오는 혼란에서 였습니다. 

    결론부터 말하자면, 자바스크립트 엔진은 한 번에 하나의 태스크만 실행할 수 있는 싱글 스레드 방식으로 동작하는게 맞고, 브라우저가 멀티 스레드로 동작하기 때문에 자바스크립트의 비동기 코드의 처리가 이루어 질 수 있는 것 이었습니다. 

     

     

    우선 자바스크립트가 동작하는 결국에는 마지막에 알게 되는 전체적인 구조를 먼저 살펴보면 아래와 같습니다.

     

     

    자바스크립트의 모든 소스 코드(전역 코드나 함수 코드 등)들은 실행에 앞서서 평가가 됩니다.

    평가를 통해서 전역 코드와 함수 코드 각각은 실행 컨텍스트를 생성하게 됩니다. 실행 컨텍스트는 자바스크립트 코드가 실행되는 환경으로 모든 JavaScript 코드는 실행 컨텍스트 내부에서 실행된다고 생각하면 됩니다. 실행 컨텍스트는 식별자(변수, 함수, 클래스 등)를 등록하고 관리하는 스코프와 코드 실행 순서를 정합니다. 그렇게 생성한 실행 컨텍스트를 실행 컨텍스트 스택(= 콜 스택)에 push 하게 됩니다. 이렇게 push 함으로써 순차적으로 코드가 실행됩니다. 

     

    그렇다면 실행 컨텍스트는 언제 생성되게 될까요?

    자바스크립트 엔진이 스크립트를 처음 마주할 때 전역 컨텍스트를 생성하고, 콜 스택에 push 합니다. 
    그러다 자바스크립트의 엔진이 함수 호출(ex. foo())을 발견할 때마다, 함수의 실행 컨텍스트를 스택에 push 합니다. 
    함수 실행 컨텍스트는 함수가 선언 될 때가 아닌 실행될 때  만들어 진다는 것을 주의해야 합니다. 

    실행 컨텍스트를 만들 수 있는 방법으로는 1. 전역 공간, 2. 함수, 3. eval() 가 있습니다.  ( eval( )은 잘 사용하지 않으므로 깊게 얘기하지 않겠습니다. )

     

    먼저 전역 실행 컨텍스트는 전역 영역에 존재하는 코드입니다. 모든 스크립트 코드는 전역 실행 컨텍스트 안에서 실행됩니다. 즉, 프로그램에 단 한개만 존재합니다. 함수 밖에 있는 코드는 전역 실행 컨텍스트에 있으며 브라우저의 경우에는 window 객체, node.js의 경우에는 global 객체가 곧 전역 실행 컨텍스트가 됩니다. 

     

    함수 실행 컨텍스트는 함수 내에서 존재하는 코드로 위에서 언급 한 것과 같이 함수가 실행될  때마다 만들어지는 실행 컨텍스트입니다. 

     

    간단한 예시를 보며 콜 스택 내에서 실행 컨텍스트가 어떻게 동작하는지 살펴봅시다.

     

    콜 스택 내에서 실행 컨텍스트가 어떻게 동작할까?

    const foo = () => {}
    const bar = () => {}
    
    foo();
    bar();

     

     

    위와 같은 자바스크립트 코드가 주어졌을때 어떻게 동작할까요? 

     

     

    콜 스택과 실행 컨텍스트

     

    먼저 전역 실행 컨텍스트가 콜 스택에 push되고 --> foo 함수와 bar 함수는 호출된 순서대로 콜 스택에 푸시되어 실행됩니다. 이렇게 콜 스택에 실행 컨택스트가 푸시되는 것이 함수 실행의 시작을 의미하기 때문에 함수의 실행 순서는 실행 컨텍스트가 관리한다고 말 할 수 있습니다. 

     

    자바스크립트 엔진은 단 하나실행 컨텍스트 만을 갖고 실행 컨텍스트 스택의 최상위 요소만이 "실행 중인 실행 컨텍스트"이고, 이를 제외한 나머지 모든 실행 컨텍스트는 모두 실행 대기 중인 상태가 되는 구조입니다. 이처럼 자바스크립트 엔진은 한 번에 하나의 태스크만 실행 할 수 있는 싱글 스레드 방식으로 동작합니다. 

     

    하지만 자바스크립트로 만든 프로그램을 크롬과 같은 브라우저로 보면 HTML 요소가 애니메이션 효과를 통해 움직이기도 하고 그 와중에 버튼을 누르면 클릭 이벤트를 처리하기도 하는 등의 처리가 동시에 일어나는 것을 볼 수 있다. 즉, 하나의 태스트가 끝나고 다음 태스크가 실행되는 방식(동기적 처리, 태스크 들이 blocking 된다.)이 아니라 현재 실행 중인 태스크가 종료되지 않은 상태라 해도 다음 태스크를 곧바로 실행하는 방식 즉, 비동기적으로 태스크가 동시적으로 처리됩니다. 

     

    위에서 보다 싶이 브라우저에 내장된 자바스크립트 엔진(ex. V8, 힙 + 콜 스택)만으로 동시적이고 비동기적인 처리를 할 없음을 느꼈을 것 입니다. 

     

    이를 위해서는,브라우저 환경이 협력해야 합니다. 브라우저 환경은 태스크 큐, 이벤트 루프, Web API 를 제공함으로써 비동기 처리를 해줍니다. 

     

    자바스크립트 코드가 비동기적으로 동작 할 수 있는 이유 브라우저의 덕분이다. 

     

    이벤트 루프와 브라우저 환경

     

    먼저 JS코드가 동작하기 위한 전체적인 구조 각각의 역할에 대해서 좀 자세히 살펴 볼게요.

     

    • 자바스크립트 엔진  
      • 실행 컨텍스트 스택 = 콜 스택
        • 실행 컨텍스트가 추가되고 제거되는 스택 자료 구조입니다. 
        • 함수가 호출되면 함수 실행 컨텍스트가 순차적으로 콜 스택에 푸시되어 순차적으로 실행됩니다. 
        • 자바스크립트 엔진은 단 하나의 콜 스택을 사용하기 때문에 최상위 실행 컨텍스트(실행 중인 컨텍스트)가 종료되어 콜 스택에서 제거되기 전까지는 다른 어떤 태스크도 실행되지 않습니다. 
        • 객체가 저장되는 메모리 공간입니다. 
        • 콜 스택의 요소인 실행 컨텍스트는 힙에 저장된 객체를 참조합니다.
         
       

    • 브라우저 환경 
      • 이벤트 루프
        •  호출 스택에 실행 중인 코드가 있는지 확인하고, 호출 스택이 비어 있다면 태스크 큐에 대기 중인 작업이 있는지 확인합니다. (지속적으로)
        • 이후 태스크 큐에서 실행 가능한 오래된 태스크부터 순차적으로 꺼내와서 콜 스택에 넣습니다. (태스크 큐가 빌 때까지 이루어진다.) 
      • 태스크 큐
        • 실행해야 테스크의 집합입니다. 
        • 이벤트 루프는 태스크 큐를 한 개 이상 가지고 있다. 
        • set 형태를 띠고 있습니다. 
          • 실행 가능한 태스크 중 가장 오래된 태스크를 꺼내와 실행합니다. 
          • 여기서 실행 가능한 태스크란 비동기 함수의 콜백 함수나 이벤트 핸들러 등을 말합니다. 
        • 태스크 큐에 들어가는 대표적인 작업 : setTimeout, setInterval, setimmediate
      • 마이크로 태스크 큐
        • 이벤트 루프는 하나의 마이크로 태스크 큐를 갖고 있는데 기존의  태스크 큐보다 우선권을 갖습니다. 
          • 즉, 마이크로 태스크 큐가 빌 때까지는 기존 태스크 큐의 실행은 뒤로 미뤄집니다. 
        • 마이크로 태스크 큐에 들어가는 대표적인 작업 : Promises, process.nextTick, queueMicroTask, MutationObserver 
      • Web API
        • Web API는 브라우저 제공 API이고 JavaScript 엔진과는 별개의 스레드에서 동작합니다. 
        • DOM API와 타이머 함수, HTTP 요청(Ajax)과 같은 비동기 처리를 포함한다. 

     

     

    예를 들어 위의 장치에 대해 더 이해해 볼게요. 

     

    function foo() {
    	console.log('foo');
    }
    
    function bar() {
    	console.log('bar');
    }
    
    setTimeout(foo, 0); // 0초 (실제로 4ms) 후에 foo 함수가 호출된다.
    bar();

     

     

     

     

    1. 전역 코드가 평가 되어 전역 실행 컨텍스트가 생성되고 콜 스택에 푸시 됩니다. 

    2.  전역 코드가 실행되기 시작되기 시작하면서 setTimeout 함수가 호출됩니다. 이때, setTimeout 함수의 실행 컨텍스트가 생성되고 콜스택에 푸시됩니다. 

    3. setTimeout 함수가 실행되면 콜백 함수를 호출 스케쥴링(타이머를 설정하고 타이머가 끝나면 콜백함수를 태스크 큐에 푸시하는 과정에 대한 설정)하고 콜 스택에서 팝 합니다. 

     

    4. a~c 과정이 진행 됩니다.

     

    5. 전역 코드 실행이 종료되고 전역 실행 컨텍스트가 콜 스태에서 팝 됩니다. 콜 스택이 비게 됩니다. 

    6. 이벤트 루프는 콜 스택이 비어 있음을 감지하고, 태스크 큐에서 대기중인 콜백 함수 foo가 이벤트 루프에 의해 콜 스택에 푸시됩니다. 다시 말해, 콜백 함수 foo의 함수 실행 컨텍스트가 생성되고 콜 스택에 푸시되어 종료 후 콜 스택에서 팝 됩니다.

     

     

    다시 정리해 보면, 자바스크립트 코드가 자바스크립트 엔진(heap, 콜 스택) 만으로 비동기 처리를 할 수 없고 브라우저(이벤트 루프, Web APIs, 태스크 큐, 마이크로 태스크 큐)와 협력 해야만 비동기 작업에 대한 처리를 할 수 있습니다. 

    반응형

    댓글

Designed by Tistory.