이벤트 루프와 태스트 분리와 웹 워커를 이용한 병렬 처리

이벤트 루프 기본 원리

  • 매크로태스크 큐에서 가장 오래된 태스크(예: <script> 실행, 사용자 이벤트, setTimeout 콜백 등)를 꺼내 실행합니다.
  • 실행 중인 매크로태스크가 끝나면 마이크로태스크 큐에 쌓인 모든 태스크(주로 프라미스의 .then 핸들러, queueMicrotask 등)를 처리합니다.
  • 모든 마이크로태스크가 완료되면, 필요한 경우 렌더링이 진행됩니다.
  • 매크로태스크 큐에 새로운 태스크가 있으면 1번으로 돌아갑니다.

매크로태스크와 마이크로태스크의 차이

매크로태스크

  • 사용자 이벤트, 네트워크 요청, setTimeout 등과 같이 브라우저 외부에서 스케줄되는 작업들이 해당합니다.
  • 태스크는 들어온 순서대로 처리되며, 각 매크로태스크 실행이 끝나면 마이크로태스크 큐를 먼저 비웁니다.

마이크로태스크

  • 프라미스 핸들러(.then, .catch, .finally), queueMicrotask()로 스케줄된 작업 등이 포함됩니다.
  • 현재 매크로태스크가 완료되면, UI 업데이트나 새로운 매크로태스크 실행 전에 마이크로태스크 큐가 모두 처리됩니다.

우선순위

  • 마이크로태스크가 매크로태스크보다 우선적으로 처리됩니다.

긴 작업 분할:매크로태스크 활용

무거운 작업 하나가 실행되는 동안 사용자 이벤트가 처리되지 않아 브라우저가 '멈춘 것처럼' 보일 수 있습니다. 이 경우 작업을 여러 작은 조각(태스크)으로 분할하면 이벤트 루프가 중간중간 다른 태스크(예: 사용자 클릭, 화면 렌더링)를 처리할 수 있습니다.

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>긴 작업 분할</title>
  <style>
    #progress { width: 400px; height: 20px; background: #ddd; position: relative; }
    #fill { height: 100%; background: #42a5f5; width: 0; }
    #status { margin-top: 5px; }
  </style>
</head>
<body>
  <h1>배열 처리 진행률</h1>
  <div id="progress"><div id="fill"></div></div>
  <div id="status">0%</div>
  <button id="start">시작</button>
  <script>
    const startBtn = document.getElementById('start');
    const fill = document.getElementById('fill');
    const status = document.getElementById('status');

    const total = 5000000;
    const chunkSize = 50000;
    let processed = 0;

    function processArray() {
      const end = Math.min(processed + chunkSize, total);
      for (let i = processed; i < end; i++) {
        // 간단한 계산 작업
        Math.pow(i, 2);
      }
      processed = end;

      const percent = (processed / total) * 100;
      fill.style.width = `${percent}%`;
      status.textContent = `${Math.floor(percent)}%`;

      if (processed < total) {
        setTimeout(processArray, 0);
      } else {
        alert('처리 완료!');
      }
    }

    startBtn.addEventListener('click', () => {
      processed = 0;
      processArray();
    });
  </script>
</body>
</html>
  • setTimeout(...,0): 작업을 청크로 분할하여 UI 반응성 유지

마이크로태스크를 사용한 비동기 실행

queueMicrotask()를 사용하면 마이크로태스크 큐에 함수를 추가할 수 있습니다. 마이크로태스크는 현재 매크로태스크가 완료된 직후, 렌더링 전에 실행되므로 상태 일관성이 보장됩니다.

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>마이크로태스크 활용</title>
  <style>
    #display { padding: 10px; border: 1px solid #ccc; }
  </style>
</head>
<body>
  <h1>상태 업데이트</h1>
  <button id="updateBtn">상태 변경</button>
  <div id="display">현재 상태: 초기값</div>
  <script>
    const updateBtn = document.getElementById('updateBtn');
    const display = document.getElementById('display');

    let state = '초기값';

    updateBtn.addEventListener('click', () => {
      state = '변경됨';
      console.log('클릭 후 상태:', state);

      queueMicrotask(() => {
        display.textContent = `현재 상태: ${state}`;
        console.log('마이크로태스크 실행:', state);
      });
    });
  </script>
</body>
</html>

웹 워커: 병렬 실행으로 부하 분산

만약 아주 무거운 연산이 필요하다면, 웹 워커를 사용하여 별도의 스레드에서 작업을 처리할 수 있습니다. 웹 워커는 메인 스레드와 독립적인 이벤트 루프를 가지며, 메시지를 통해 메인 스레드와 데이터를 주고받을 수 있습니다. 단, 웹 워커는 DOM에 직접 접근할 수 없으므로, UI 업데이트가 필요한 경우 메인 스레드와의 협력이 필요합니다.

<!-- worker.js -->
self.onmessage = (e) => {
  const n = e.data;
  function fib(n) {
    return n <= 1 ? n : fib(n - 1) + fib(n - 2);
  }
  self.postMessage(fib(n));
};
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>웹 워커 피보나치</title>
</head>
<body>
  <h1>피보나치 계산</h1>
  <input type="number" id="numberInput" value="40" min="1">
  <button id="calcBtn">계산</button>
  <div id="result">결과: </div>
  <script>
    const calcBtn = document.getElementById('calcBtn');
    const numberInput = document.getElementById('numberInput');
    const result = document.getElementById('result');

    calcBtn.addEventListener('click', () => {
      const worker = new Worker('worker.js');
      worker.postMessage(parseInt(numberInput.value));
      worker.onmessage = (e) => {
        result.textContent = `결과: ${e.data}`;
        worker.terminate();
      };
      worker.onerror = (e) => console.error('워커 오류:', e);
    });
  </script>
</body>
</html>