
🎯 수도쿠 문제를 Wasm과 자바스크립트로 각각 풀며, 성능 차이를 비교합니다.
해당 글에서는 서브 스레드에서 로직을 실행할 수 있게 돕는 worker에 대해서 알아보고, worker를 사용하여 js로 수도쿠 문제를 풀고, 푼 결과를 브라우저에 반영하는 과정을 담고 있습니다.
💡 웹 프론트엔드 개발자의 관점에서 워커란?
현대 웹 앱은 유저와 수많은 상호작용이 있고, 그에 따른 UI 업데이트가 즉시 일어나야 한다. 그렇기 때문에 UI를 변경하는 메인 쓰레드는 유휴 상태로 유지하는 것이 중요하다.
메인 쓰레드를 유휴 상태로 유지하기 위해 모든 js 로직을 워커로 실행하자는 말은 아니다. 워커를 사용하는 데에도 비용이 든다.
- 워커는 일부 호스트 API에 접근이 불가능하다.
- 서브 스레드와 메인 스레드 간의 통신 비용이 든다.
결국 CPU 집약적이거나 오랜 시간이 소요되는 작업만 워커로 실행하는 편이 합리적이라 생각한다.
워커는 이벤트 기반 아키텍처를 따르며, 이를 통해 비동기적인 작업을 처리하고 메인 스레드와 효율적으로 상호작용할 수 있다.
워커 스크립트는 export하지 않는다. 워커는 주로 백그라운드에서 비동기적으로 실행되기 때문에 모듈을 내보내지 않고 사용한다. 대신 메인 스레드와 통신을 위해 postMessage, onmessage를 사용한다.
main.js
const { Worker } = require("worker_threads");
const worker = new Worker("./worker.js");
// 워커 이벤트 핸들러 등록
worker.on("message", (value) => {
console.log(`부모[수신] : ${value}`);
});
worker.on("exit", () => {
console.log("부모[수신] : 워커 종료 이벤트");
console.log("");
});
// 워커에게 메시지 보내기
const msg = "워커야 이 메시지 받을 수 있어 ?";
console.log(`부모[송신] : ${msg}`);
worker.postMessage(msg);
worker.js
const { parentPort } = require("worker_threads");
parentPort.on("message", (value) => {
console.log(`자식[수신] : ${value}`);
const msg = `부모님, 이 메시지 받으셨나요?`;
console.log(`자식[송신] : ${msg}`);
parentPort.postMessage(msg);
console.log("자식[송신] : 워커 종료 이벤트");
parentPort.close();
});


pnpm i comlink
pnpm i -D vite-plugin-comlink
vite.config.ts
export default defineConfig({
plugins: [react(), comlink()],
worker: {
plugins: () => [comlink()],
},
});
vite-env.d.ts
/// <reference types="vite/client" />
/// <reference types="vite-plugin-comlink/client" /> 👈👈👈
worker.ts
export const workerInstance = new ComlinkWorker<
typeof import("수행할_작업이_있는_곳")
>(new URL("수행할_작업이_있는_곳", import.meta.url));
sudokuSolve.ts
export const sudokuSolve = (initialBoard: Board) => {
// ...
return copyBoard;
}
sudoku.tsx
import { workerInstance } from "../../worker/js";
...
useEffect(() => {
(async function (){
workerInstance.sudokuSolve(board);
})();
},[])
return (...)
