프론트엔드 개발자라면 모두 알다시피 Javascript는 싱글스레드 언어입니다.Javascript는 싱글스레드이기 때문에 한계가 명확히 존재합니다. 그렇기 때문에 작업들을 병렬로 처리할 수 없고, 다른 작업이 끝날 때까지 기다려야합니다.
이를 개선하기 위해 JS는 비동기
라는 작업을 통해 런타임 환경에 위임하고 처리합니다. 우리가 보통 말하는 런타임은 아래와 같이 구성되어 있습니다.
[메인 스레드]
├─ Call Stack
├─ Web APIs
├─ Task Queue
├─ Microtask Queue
└─ Event Loop
메인 스레드가 위 작업들을 순차적으로 처리하는데 이 작업이 많아지면 많아질수록 메인 스레드는 병목 현상을 겪게 됩니다. 그리고 이는 결과적으로 불러와야 할 데이터, 처리해야 할 이벤트가 늦게 처리되어 UX가 저하된다는 문제점이 발생하게 됩니다.
이러한 문제점을 Web Worker가 해결해줄 수 있습니다!
Web Worker란 Javascript에서 사용할 수 있는 백그라운드 스레드 실행 환경입니다. 브라우저의 메인 스레드와 완전히 분리되어 비동기 작업을 처리할 수 있습니다.
앞서 말한대로 JS는 싱글 스레드 언어입니다. 하지만 우리의 브라우저는 멀티 스테드 환경입니다. JS 엔진(V8엔진)은 싱글 스레드이지만 브라우저는 렌더링 스레드, 네트워크 스레드, 타이머 스레드, Web Worker 스레드 등 여러 스레드를 가지고 있습니다.
navigator.hardwareConcurrency
를 사용하면 현제 스레드의 개수를 확인할 수 있습니다.
같은 출처(origin)를 가진 여러 탭, iframe, window, Web Worker에서 공유할 수 있는 워커입니다.
예를 들어, 여러 탭에서 공통 데이터를 캐싱하거나 동기화할 때 사용할 수 있습니다.
워커 종류 | 특징 및 설명 |
---|---|
Dedicated Worker (전용 워커) | 한 개의 스크립트(혹은 탭)에서만 사용하는 워커입니다. 생성한 곳(부모)과 1:1로 메시지를 주고받으며, 복잡한 계산이나 데이터 처리를 메인 스레드와 분리해서 실행할 때 주로 사용합니다. 메인 스레드와 워커 간의 postMessage를 통해 양방향 통신을 진행할 수 있습니다. |
Shared Worker (공유 워커) | 같은 도메인 내 여러 창, 탭, iframe 등에서 공유할 수 있는 워커입니다. 여러 스크립트가 동시에 접근 가능하며, active port 로 통신합니다. 여러 곳에서 동일한 백그라운드 작업이 필요할 때 유용합니다. |
Service Worker (서비스 워커) | 웹 앱과 브라우저(또는 네트워크) 사이에서 프록시 서버처럼 동작합니다. 네트워크 요청을 가로채거나, 오프라인 동작, 캐싱, 푸시 알림 등 백그라운드 작업에 사용됩니다. DOM에 접근할 수 없고, 탭이 모두 닫혀도 백그라운드에서 동작할 수 있습니다. Progressive Web App(PWA)의 핵심 기술입니다. (localhost를 제외하고 https에서만 사용할 수 있습니다.) |
사용자로부터 base64 문자열을 받았을 때 해당 문자열을 Web Worker에서 File객체로 변환하고 메인 스레도로 전달합니다.
// 워커에서 실행되는 코드
self.onmessage = function(event) {
const { base64, fileNm, mimeType } = event.data;
try {
const splitBase64 = base64.split(',')[1] ? base64.split(',')[1] || base64;
// base64, 바이너리 변환
const binary = atob(splitBase64);
const byteArray = new Uint8Array([...binary].map(char => char.charCodeAt(0)));
const blob = new Blob([byteArray], { type: mimeType });
const file = new File([blob], fileNm, { type: mimeType });
self.postMessage({ success: true, file });
} catch (error) {
self.postMessage({ success: false, error: error.message });
}
};
Vue3에서 웹 워커를 사용하여 base65를 파일로 변환하고 결과를 처리하는 컴포넌트입니다.
<template>
<div>
<h1>Base64 파일 변환</h1>
<input type="file" @change="handleFileChange" />
<div v-if="convertedFile">
<h3>변환된 파일</h3>
<a :href="convertedFileUrl" download>다운로드</a>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
// import from '../'
// 상태 변수 선언
const convertedFile = ref(null);
const convertedFileUrl = ref(null);
// 웹 워커 인스턴스 생성
const worker = new Worker(new URL('../utils/base64-worker.js', import.meta.url));
// base64 → 파일로 변환하는 함수
const convertBase64ToFile = (base64, fileNm, mimeType) => {
return new Promise((resolve, reject) => {
worker.onmessage = (e) => {
if (e.data.success) resolve(e.data.file);
else reject(new Error(e.data.error));
};
worker.postMessage({ base64, fileNm, mimeType });
});
};
// 파일 선택 시 호출되는 메서드
const handleFileChange = async (event) => {
const file = event.target.files[0];
if (!file) return;
// 파일을 base64로 변환
const base64 = await fileToBase64(file);
try {
// base64 → 파일 변환
const mimeType = file.type;
const convertedFileObj = await convertBase64ToFile(base64, file.name, mimeType);
// 변환된 파일 URL 생성
convertedFile.value = convertedFileObj;
convertedFileUrl.value = URL.createObjectURL(convertedFileObj);
} catch (error) {
console.error('파일 변환 실패:', error.message);
}
};
// 파일을 base64로 변환하는 함수
const fileToBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
};
</script>
OffscreenCanvas 인터페이스는 화면 밖에서 렌더링할 수 있는 캔버스를 제공하고 DOM과 Canvas API를 분리하여
canvas
요소가 DOM에 완전히 의존하지 않도록 합니다. 렌더링 작업은 worker 맥락 내에서 실행할 수도 있어서 별도의 스레드에서 일부 작업을 실행하고 메인 스레드에서 무거운 작업을 피할 수 있습니다.
Web Worker는 다른 스레드를 사용할 수 있게하기에 마냥 좋은것같이 보이지만 무분별하게 사용하면 메모리의 사용량이 늘어나 오히려 성능의 저하를 초래할 수 있습니다. 때문에 적절한 사용과 사용이 필요 없을 때 꼭 종료를 해주는 것이 필요합니다.
또한 DOM에 접근을 할 수 없기 때문에 메인 스레드에서 백그라운드에 요청을 보낸 뒤, 백그라운드에서 데이터를 처리 및 가공 후 다시 전송하는 과정이 필요합니다.
위 내용과 유사하게 메인스레드에 종속되지 않기 때문에 상태(state) 변화가 즉각적으로 전달되지 않습니다. web worker의 postMessage와 onMessage를 활용하여 처리해야합니다.
Web Worker는 메인 스레드에서 처리할 작업을 적절하게 분리하여 병목 현상을 줄이고 사용자 UX를 개선할 수 있습니다. 위에서는 파일 다운로드 로직만 구현했지만 Jpeg, Png 파일을 Webp로 변환하거나 setInterval를 사용한 타이머를 제작하는 등 다양한 방면으로 사용할 수 있습니다.