싱글 스레드의 경우 기본적으로 연산량이 많은 작업을 하는 경우, 그 작업이 완료되고 다른 작업을 수행할 수 있다.
setInterval과 같은 함수로 반복 로직을 구현한 경우 브라우저의 정책으로 인해 쓰로틀링이 걸려버릴 수 있습니다.
예를 들어 브라우저 탭이 비활성화 될 경우 크롬의 경우 시간이 멈추고, 사파리는 시간이 1.5배속으로 흐르는 등... 결과가 전혀 보장되지 않을 수 있다.
쓰로틀링이란?
쓰로틀링이란 브라우저가 자체적으로 판단했을 때 비활성화 된 탭에서 반복적으로 발생하는 이벤트가 있다면 그 이벤트에 delay등을 줘서 결국 해당 이벤트를 무시하고 메모리 자원을 다른 곳에 쓰는 등 메모리를 효율적으로 관리하는 방법이다. 어떻게 보면 브라우저의 입장에서는 이해가 되는 부분이지만 로직을 짜는 입장에서는 답답할 수 밖에 없다. 정말 계속해서 몇초마다 돌아야 하는 기능을 구현을 하게 된다면 이는 큰 문제가 될 것이다.
Web Worker와 관련한 간단한 워크플로우
자바스크립트는 브라우저에서 싱글 스레드 기반으로 동작한다.
따라서, Web Worker를 사용하지 않았을 때에는 UI 동작과 데이터 핸들링이 순차적인 방식으로 처리가 되고,
Web Worker를 사용하면 병렬 처리가 가능하다.
자바스크립트 엔진 동작 방식
일반적인 이벤트 처리
를 담당하는 태스크 큐, Promise와 같은 특별한 비동기 처리
를 담당하는 마이크로태스크 큐, 그리고 부드러운 화면 전환
을 위한 애니메이션 프레임 큐가 있다. 각각의 큐는 자신만의 고유한 역할과 우선순위를 가지고 있다.
이벤트 루프는 이러한 큐들과 콜 스택을 지속적으로 감시하면서 작업을 조율한다. 콜 스택이 비어있을 때, 가장 먼저 마이크로태스크 큐의 모든 작업을 처리하고, 그 다음으로 태스크 큐의 작업을 하나 처리하며, 마지막으로 애니메이션 프레임의 작업을 처리한다
사용자가 직접 데이터를 처리하는 부분은 싱글 스레드에서 동작되므로 데이터 처리가 많아질수록 병목현상이 생겨 렌더링 프레임에 영향을 미치게 된다.
병목현상을 처리하기 위해서 Web Worker를 사용하게 된다.
워커 스레드들은 메인 스레드를 방해하지 않고 복잡한 계산이나 데이터 처리 같은 무거운 작업을 처리할 수 있다.
메인 스레드와 워커 스레드는 'postMessage'라는 방식으로 서로 통신한다. 직접적인 메모리 공유 대신 메시지 전달 방식을 사용함으로써 각 스레드의 독립성을 보장하고 동시에 안전한 데이터 교환을 가능하게 한다.
메시지가 전달되면 각 스레드의 메시지 큐에 저장되고, 해당 스레드의 이벤트 루프가 이를 처리하게 된다.
/* main.js */
// 웹 워커 생성
const worker = new Worker('worker.js');
// 워커로 메시지 전송
worker.postMessage({
number: 100000000
});
// 워커로부터 메시지 수신
worker.onmessage = function(e) {
console.log('결과 값:', e.data);
worker.terminate(); // 워커 종료
};
/* worker.js */
// 메인 스레드로부터 메시지 수신
self.onmessage = function(e) {
const number = e.data.number;
// 시간이 오래 걸리는 작업 예시 (1부터 주어진 숫자까지의 합)
let sum = 0;
for(let i = 1; i <= number; i++) {
sum += i;
}
// 결과를 메인 스레드로 전송
self.postMessage(sum);
};