[카테고리:] 미분류

  • JavaScript 비동기 처리의 내부 동작 메커니즘

    JavaScript의 비동기 처리 시스템은 아키텍처적으로 정교하게 설계되어 있습니다. 이 시스템의 핵심 컴포넌트와 실행 흐름을 분석해보겠습니다.

    이벤트 루프 아키텍처

    JavaScript는 싱글 스레드 언어이지만, 호스트 환경(브라우저/Node.js)은 멀티스레딩을 활용합니다:

    +--------------------------------------------------+
    |                   JavaScript 런타임               |
    |  +-----------+    +-------------------------+    |
    |  | 콜 스택    |    |        힙(메모리)        |    |
    |  +-----------+    +-------------------------+    |
    +--------------------------------------------------+
               |                    ^
               v                    |
    +--------------------------------------------------+
    |              Web APIs / Node.js APIs             |
    |  +--------+  +--------+  +---------------------+ |
    |  | DOM    |  | AJAX   |  | setTimeout/timers   | |
    |  +--------+  +--------+  +---------------------+ |
    +--------------------------------------------------+
               |                    ^
               v                    |
    +--------------------------------------------------+
    |                   이벤트 루프                     |
    |  +------------------+  +---------------------+   |
    |  | 태스크 큐(매크로) |  | 마이크로태스크 큐    |   |
    |  +------------------+  +---------------------+   |
    +--------------------------------------------------+
    

    실행 흐름의 정밀 분석

    1. 태스크 스케줄링 메커니즘:
      • 비동기 작업은 호스트 환경(브라우저/Node.js)의 스레드풀로 오프로드됩니다.
      • JavaScript 메인 스레드는 블로킹되지 않고 계속 실행됩니다.
    2. Promise와 마이크로태스크 우선순위: console.log('Script start'); setTimeout(() => { console.log('setTimeout'); // 매크로태스크 }, 0); Promise.resolve().then(() => { console.log('Promise'); // 마이크로태스크 }); console.log('Script end'); // 출력: "Script start", "Script end", "Promise", "setTimeout" 마이크로태스크는 각 매크로태스크 완료 후 큐가 비워질 때까지 처리되므로 우선순위가 높습니다.
    3. async/await 변환 과정: async function example() { const result = await asyncOperation(); return result; } 컴파일러는 이를 다음과 같이 변환합니다: function example() { return new Promise((resolve, reject) => { asyncOperation() .then(result => { resolve(result); }) .catch(err => { reject(err); }); }); }

    이벤트 루프 실행 알고리즘 (간략화)

    while (true) {
      // 콜 스택이 비워질 때까지 현재 태스크 실행
      while (callStack.isNotEmpty()) {
        executeCurrentTask();
      }
      
      // 모든 마이크로태스크 처리
      while (microtaskQueue.isNotEmpty()) {
        executeNextMicrotask();
      }
      
      // 렌더링 업데이트 (브라우저에만 해당)
      if (shouldRender()) {
        render();
      }
      
      // 다음 매크로태스크 실행 준비
      if (taskQueue.isNotEmpty()) {
        task = taskQueue.dequeue();
        pushToCallStack(task);
      } else {
        // 대기
        wait();
      }
    }
    

    브라우저와 Node.js의 주요 차이점

    브라우저와 Node.js는 둘 다 이벤트 루프를 사용하지만, 구현 방식에 차이가 있습니다:

    • 브라우저: 단일 이벤트 루프 + 렌더링 엔진과 통합
    • Node.js: libuv 라이브러리 기반 이벤트 루프 + 여러 단계의 큐(phases)로 구성:
      1. timers
      2. pending callbacks
      3. idle, prepare
      4. poll
      5. check
      6. close callbacks

    이러한 아키텍처는 JavaScript가 싱글 스레드임에도 불구하고 효율적인 비동기 처리를 가능하게 하며, 백그라운드 스레드풀을 활용하여 CPU 바운드 작업과 I/O 바운드 작업을 모두 효과적으로 처리합니다.