본문 바로가기
개발/자바스크립트

[Web worker] 웹워커 간단 사용기

by 핸디(Handy) 2023. 9. 4.

web worker라는 키워드로 만들었더니 노동자가 나옴..

들어가며

이번 글은 web worker(이하 웹 워커)에서 postMessage에 대한 에러를 확인하고 이를 수정하는 방법론? 에 대한 글입니다.

이 글에선 웹 워커의 간단한 동작,worker와의 통신, react에서 사용할 때 주의해야 할 점을 다룹니다.

Web worker란

web.dev 문서를 잡고하면 웹 워커는  웹 애플리케이션에서 백그라운드 스크립트를 생성하기 위한 API입니다.

이 말보다는 저는 웹 워커는 자바스크립트에 없는 동시성을 주는 브라우저의 기능이다라고 설명합니다.

자바스크립트의 한계

웹 워커는 자바스크립트의 언어 자체가 가진 문제점으로 인해 만들어졌습니다.

자바스크립트는 단일 스레드 환경으로 여러 가지 스크립트를 동시에 실행할 수 없습니다.

그래서 비동기 코드를 작성하거나 setTimeout 등으로 단일스레드의 문제점을 우회해서 해결해야 합니다.

근데 단순히 계산이 오래 걸리는 작업은 어떻게 해야 할까요?

피보나치수열의 경우 특정한 방법을 쓰면 엄청난 최적화가 가능합니다.

하지만 단순히 수십만 개의 숫자를 더하거나 곱하거나 하는 단순 계산은 최적화할 여지가 없다고 봐야 합니다.

물론 이 경우도 서버에서 처리해서 결과를 받는다면 간단히 해결되지만 가끔씩 서버보단 클라이언트단에서 모든 것을 해결해야 할 때가 있습니다.

이럴 때 쓰는 게 바로 웹 워커입니다.

Web worker 사용하기

웹워커의 기본 사용법을 살펴보고 가시죠.

기본 사용법

// 워커 생성
const worker = new Worker('sample-worker.js');

// 워커에 메시지 보내기
worker.postMessage(); 

// 워커의 메시지 수신
worker.addEventListener('message', function(e) {
	console.log('Worker said: ', e.data);
}, false);

간단하죠?

일반 것으로 postMessage의 구조는 react의 useReducer를 생각하면 됩니다.

type과 state를 주는 방식으로 구현하죠.

아래는 실제 제가 사용하는 프로젝트에서 worker 코드입니다.

이렇게 type을 정확하게 정의해서 하는 것이 필요합니다.

타입스크립트 사용법

// web worker를 가져와서 사용하는 곳

const audioEditorWorker = new Worker(
  new URL("/src/worker/AudioEditor.worker.ts", import.meta.url)
);

audioEditorWorker.postMessage({
  type: "pushOriginAudioBuffer",
  state : {/* 중략 */}
});

audioEditorWorker.postMessage({
  type: "pushExtraData",
  state : {/* 중략 */}
});


audioEditorWorker.addEventListener("message", (event) => {
  if (event?.data?.type === "audioBufferToMp3Result") {
    // do something
  }
  if (event?.data?.type === "audioBufferToMp3Progress") {
    // do something2
  }
});

 

// src/worker/AudioEditor.worker.ts

const ctx: Worker = self as unknown as Worker;

ctx.addEventListener("message", (evt) => {
  switch (evt.data?.type) {
    case "pushOriginAudioBuffer": {
      doSomething(evt.data?.state)
      return;
    }
    case "pushEditedSegmentInfo": {
      doSomething2(evt.data?.state)
      return;
    }
    case "pushExtraData": {
      doSomething2(evt.data?.state)
      return;
    }
  }
});

const doSomeThing = (state: any) => {
  /* 중략 */
  ctx.postMessage({
    type: "audioBufferToMp3Result",
    result: {
      /* 중략 */
    },
  });
};


export {};

이렇게 되어있습니다.

결국엔 사용하는 곳, worker 모두 postMessage와 addEventListener가 있어야 합니다.

Web worker 사용팁

워커를 사용하면서 나왔던 에러 및 팁을 언급해 보겠습니다.

사용 후 제거하기

많은 예시들이 사용하는 것까지만 되어있고 제거하는 부분에 대한 언급은 빠져있습니다.

워커를 제거하지 않으면 계속 쌓입니다. 쌓였을 때 어떤 일이 발생할지 모르겠으나 예방 차원에서 사용한 뒤에는 꼭 제거합시다.

제거하는 방법은 간단합니다.

// 워커를 불러오고
const audioEditorWorker = new Worker(
  new URL("/src/worker/AudioEditor.worker.ts", import.meta.url)
);

/* 워커를 많이 많이 사용하고 */

/* 마지막엔 terminate 호출*/
audioEditorWorker.terminate();

ㅇㅇㅇ

postMessage 분할요청

이건 제가 발견한 이슈인데요.

오디오를 다루는 프로젝트답게 어느 순간 들고 있는 데이터용량이 수백 M B가 되곤 합니다. 

수십 개의 오디오파일을 수정하기 위해 web worker를 사용하는데. 제 기준으로 20개의 파일을 postMessage로 보내는데 이상이 없었습니다.

그런데 어느 순간 동작을 안 하고 에러메시지도 보내지 않는 버그가 생겨났습니다.

여러 가지 이유를 고민하다 postMessage의 파라미터의 최대사이즈가 있지 않을까 고민을 하고 찾아보았는데요.

 

What is the maximum size for the postMessage method that enables inter-frame communication?

It's not clear from searching on Google and looking through documentation. What's the maximum length on a message sent via Window.postMessage (https://developer.mozilla.org/en-US/docs/Web/API/Window.

stackoverflow.com

스펙기준으로는 없다고 나옵니다.

근데 결론부터 말하면 수정하니깐 동작했습니다.

근데 왜 안될까요... 그래서 일단 postMessage을 분할해서 날려보도록 수정했습니다.

// 첫번째 파라미터 보내기
audioEditorWorker.postMessage({
  type: "pushOriginAudioBuffer",
  state : {}
});

// 두번째 파라미터 보내기
audioEditorWorker.postMessage({
  type: "pushExtraData",
  state: {},
});

// 루프 돌면서 보내기
array.forEach((item, index) => {
  audioEditorWorker.postMessage({
    type: "pushEditedSegmentInfo",
    state: {
      index :index,
      content : item
    },
  });
});

// 다 보냈다고 뭔갈 실행하라는 명령
audioEditorWorker.postMessage({
  type: "run",
});

받는 쪽에서는 객체를 하나 선언하고 들어온 값을 조합한 다음 마지막에 명령이 들어왔을 때 진행하고 응답해 주면 되겠습니다.

const 완성된_오브젝트 = {};

ctx.addEventListener("message", (evt) => {
  switch (evt.data?.type) {
   /* 중략 */
    case "run": {
      doSomeThing(완성된_오브젝트);
      return;
    }
  }
});

가장 중요한 팁

마지막으로 가장 중요한 팁을 전해드리고 마무리하겠습니다.

 

The Basics of Web Workers

 

web.dev

아주아주 잘 정리된 문서인데 대충 3 회독하시면 웹워커 고수가 될 수 있습니다.

 

마무리

이번글에서 웹워커의 사용법부터 제가 느낀 팁? 에러까지 빠르게 살펴보았습니다.

기술적으로 딥한 내용을 다루기엔 저도 아직 웹워커를 프로젝트에선 3번밖에 안사용해 봐서 미숙하기에 이번에는 간단 리뷰해 보았습니다.

요새 핫한 생성 AI모델 중에는 가벼운 모델의 경우 웹어셈블리 + 웹워커 조합으로 안정적으로 돌리는 방법들이 속속들이 나오고 있습니다.

이번 기회에 웹워커에 대한 공부를 해놓시면 언젠가 꼭 사용하실 것이라 생각됩니다.

끝.

 

댓글