타입스크립트에서의 웹 워커

타입스크립트에서의 웹 워커

이 기사에서는 타입스크립트에서 웹 워커에 대해 설명합니다.

Web Worker의 개념과 다양한 사용 팁을 예제를 통해 배울 수 있습니다.

YouTube Video

타입스크립트에서의 워커

타입스크립트에서 Worker는 JavaScript 웹 워커 API를 활용하여 메인 스레드와 분리된 백그라운드에서 처리를 수행하는 메커니즘입니다. 이를 통해 무거운 계산 작업과 비동기 태스크를 사용자 인터페이스의 동작에 영향을 주지 않고 실행할 수 있습니다.

Worker는 메인 스레드(UI 스레드)와 병렬로 동작하며 메시지를 통해 스레드 간 데이터를 교환할 수 있습니다. 타입스크립트에서도 타입 안전한 코드를 작성하면서 Worker를 사용할 수 있습니다.

워커의 기본 사용법

  1. 워커 생성하기

    Worker 인스턴스를 생성하고 지정된 스크립트를 실행합니다. 일반적으로 스크립트는 별도의 파일로 정의됩니다.

  2. 메시지 교환

    postMessageonmessage를 사용하여 메인 스레드와 Worker 스레드 간 메시지를 주고받습니다.

예제: 기본적인 워커 구현

  1. worker.ts: 워커를 위한 스크립트
1// worker.ts
2self.onmessage = (event) => {
3    const data = event.data;
4    const result = data.num1 + data.num2;
5    self.postMessage(result); // Return the result to the main thread
6};
  1. main.ts: 메인 스레드에서 워커를 사용하는 스크립트
 1// main.ts
 2const worker = new Worker(
 3    new URL('./worker.ts', import.meta.url),
 4    { type: 'module' }
 5);
 6
 7worker.onmessage = (event) => {
 8    console.log("Result from worker:", event.data); // Receive message from the worker
 9};
10
11worker.postMessage({ num1: 10, num2: 20 }); // Send message to the worker
  • 이 예제에서 worker.ts는 별도의 스레드에서 실행되며, num1num2의 합계를 계산하여 메인 스레드로 반환합니다. 메인 스레드는 결과를 받아서 콘솔에 출력합니다.
  • type: 'module'을 지정하면, 워커 스크립트가 ES 모듈로 해석되어 importexport를 사용할 수 있습니다. 이렇게 하면 기존의 importScripts()를 사용하지 않고도 모듈 구조를 처리할 수 있습니다.

타입스크립트에서 워커 사용 시 주의할 점

타입 정의 추가하기

타입스크립트에서는 메시지를 송수신할 때 타입 안전한 상호작용을 보장하기 위해 데이터 타입을 정의합니다.

 1// Define data types
 2interface WorkerData {
 3    num1: number;
 4    num2: number;
 5}
 6
 7interface WorkerResult {
 8    result: number;
 9}
10
11// worker.ts
12self.onmessage = (event: MessageEvent<WorkerData>) => {
13    const data = event.data;
14    const result = data.num1 + data.num2;
15    const message: WorkerResult = { result };
16    self.postMessage(message); // Send the result in a type-safe manner
17};
18
19// main.ts
20const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
21
22// Type the event from the worker
23worker.onmessage = (event: MessageEvent<WorkerResult>) => {
24  console.log("Result from worker:", event.data); // event.data is number
25};
26
27// Send typed data to the worker
28const message: WorkerData = { num1: 10, num2: 20 };
29worker.postMessage(message);
  • MessageEvent의 타입 매개변수를 지정하여 수신할 데이터의 타입을 명확히 정의할 수 있습니다. 이를 통해 타입 안전성을 갖춘 데이터 교환이 가능합니다.

Webpack 또는 Vite 설정하기

TypeScript에서 Worker를 사용할 때, Webpack이나 Vite와 같은 번들러가 필요할 수 있습니다. 이 도구들을 사용하면 Worker 스크립트를 올바르게 번들링하고 메인 스레드에서 사용할 수 있습니다.

예를 들어, Vite를 사용할 때는 import.meta.url을 활용하여 Worker를 올바르게 가져올 수 있습니다.

1const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
  • 이렇게 하면 번들링된 Worker 스크립트가 올바르게 로드되어 Worker를 활용한 처리가 가능합니다.

메시징 및 동시성에 대한 고려사항

  1. 데이터 복사

    메인 스레드와 Worker 스레드 간 메시지를 주고받을 때 데이터가 복사됩니다. 객체와 같은 복잡한 데이터를 다룰 때는 효율성을 고려해야 합니다. 대량의 데이터를 자주 교환하면 성능이 저하될 수 있습니다.

  2. 전송 가능한 객체

    ArrayBuffer와 같은 일부 객체는 Transferable 객체라고 불립니다. Transferable 객체는 메시지 전송 시 복사가 아니라 Worker로 전송될 수 있습니다. 이를 통해 데이터 복사에 따른 오버헤드를 피할 수 있습니다.

1const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
2
3const buffer = new ArrayBuffer(1024);
4worker.postMessage(buffer, [buffer]); // Transfer ownership to the Worker
5
6console.log(buffer.byteLength); // 0 (ownership moved)
  • 두 번째 인자로 [buffer]worker.postMessage()에 전달하면, buffer는 복사되지 않고 Worker로 전송(이관)됩니다.

  • 이후 메인 스레드의 buffer는 비어 있게 되며(byteLength가 0) Worker만 사용할 수 있게 됩니다.

Worker 종료하기

Worker를 사용한 후 종료하면 메모리 소비를 최소화할 수 있습니다. terminate 메서드를 사용하여 Worker를 종료할 수 있습니다.

1const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
2// ...
3
4worker.terminate(); // Terminate the Worker
  • 이 코드는 terminate 메서드를 사용하여 Worker를 종료합니다.

Worker에서의 예외 처리

Worker 내에서 오류가 발생하면 onerror 이벤트를 사용하여 오류를 처리할 수 있습니다.

1const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
2
3worker.onerror = (error) => {
4    console.error("Error in Worker:", error.message);
5};
  • 이 코드는 onerror 이벤트를 사용하여 Worker 내부에서 발생한 오류를 감지하고 처리합니다.

요약

TypeScript에서 Worker를 사용하면 백그라운드에서 무거운 작업을 수행하면서 메인 스레드를 부드럽게 유지할 수 있습니다. 타입 정의를 사용하면 메시지 교환도 타입 안전하게 수행할 수 있습니다. 데이터 교환과 스레드 관리를 신경 쓰면 성능 개선과 효율적인 동시성을 달성할 수 있습니다.

위의 기사를 보면서 Visual Studio Code를 사용해 우리 유튜브 채널에서 함께 따라할 수 있습니다. 유튜브 채널도 확인해 주세요.

YouTube Video