자바스크립트에서의 웹 워커

자바스크립트에서의 웹 워커

이 글은 자바스크립트에서의 웹 워커에 대해 설명합니다.

YouTube Video

javascript-web-worker.html
  1<!DOCTYPE html>
  2<html lang="en">
  3<head>
  4  <meta charset="UTF-8">
  5  <title>JavaScript &amp; HTML</title>
  6  <style>
  7    * {
  8        box-sizing: border-box;
  9    }
 10
 11    body {
 12        margin: 0;
 13        padding: 1em;
 14        padding-bottom: 10em;
 15        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 16        background-color: #f7f9fc;
 17        color: #333;
 18        line-height: 1.6;
 19    }
 20
 21    .container {
 22        max-width: 800px;
 23        margin: 0 auto;
 24        padding: 1em;
 25        background-color: #ffffff;
 26        border: 1px solid #ccc;
 27        border-radius: 10px;
 28        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
 29    }
 30
 31    .container-flex {
 32        display: flex;
 33        flex-wrap: wrap;
 34        gap: 2em;
 35        max-width: 1000px;
 36        margin: 0 auto;
 37        padding: 1em;
 38        background-color: #ffffff;
 39        border: 1px solid #ccc;
 40        border-radius: 10px;
 41        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
 42    }
 43
 44    .left-column, .right-column {
 45        flex: 1 1 200px;
 46        min-width: 200px;
 47    }
 48
 49    h1, h2 {
 50        font-size: 1.2rem;
 51        color: #007bff;
 52        margin-top: 0.5em;
 53        margin-bottom: 0.5em;
 54        border-left: 5px solid #007bff;
 55        padding-left: 0.6em;
 56        background-color: #e9f2ff;
 57    }
 58
 59    button {
 60        display: block;
 61        margin: 1em auto;
 62        padding: 0.75em 1.5em;
 63        font-size: 1rem;
 64        background-color: #007bff;
 65        color: white;
 66        border: none;
 67        border-radius: 6px;
 68        cursor: pointer;
 69        transition: background-color 0.3s ease;
 70    }
 71
 72    button:hover {
 73        background-color: #0056b3;
 74    }
 75
 76    #output {
 77        margin-top: 1em;
 78        background-color: #1e1e1e;
 79        color: #0f0;
 80        padding: 1em;
 81        border-radius: 8px;
 82        min-height: 200px;
 83        font-family: Consolas, monospace;
 84        font-size: 0.95rem;
 85        overflow-y: auto;
 86        white-space: pre-wrap;
 87    }
 88
 89    .highlight {
 90        outline: 3px solid #ffc107; /* yellow border */
 91        background-color: #fff8e1;  /* soft yellow background */
 92        transition: background-color 0.3s ease, outline 0.3s ease;
 93    }
 94
 95    .active {
 96        background-color: #28a745; /* green background */
 97        color: #fff;
 98        box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
 99        transition: background-color 0.3s ease, box-shadow 0.3s ease;
100    }
101  </style>
102</head>
103<body>
104    <div class="container">
105        <h1>JavaScript Console</h1>
106        <button id="executeBtn">Execute</button>
107        <div id="output"></div>
108    </div>
109
110    <script>
111        // Override console.log to display messages in the #output element
112        (function () {
113            // Override console.log
114            const originalLog = console.log;
115            console.log = function (...args) {
116                originalLog.apply(console, args);
117                const message = document.createElement('div');
118                message.textContent = args.map(String).join(' ');
119                output.appendChild(message);
120            };
121
122            // Override console.error
123            const originalError = console.error;
124            console.error = function (...args) {
125                originalError.apply(console, args);
126                const message = document.createElement('div');
127                message.textContent = args.map(String).join(' ');
128                message.style.color = 'red'; // Color error messages red
129                output.appendChild(message);
130            };
131        })();
132
133        document.getElementById('executeBtn').addEventListener('click', () => {
134            // Prevent multiple loads
135            if (document.getElementById('externalScript')) return;
136
137            const script = document.createElement('script');
138            script.src = 'javascript-web-worker.js';
139            script.id = 'externalScript';
140            //script.onload = () => console.log('javascript-web-worker.js loaded and executed.');
141            //script.onerror = () => console.log('Failed to load javascript-web-worker.js.');
142            document.body.appendChild(script);
143        });
144    </script>
145</body>
146</html>

JavaScript에서의 Web Worker

JavaScript의 Web Worker는 코드가 메인 스레드와 분리된 별도의 스레드에서 실행될 수 있게 해주는 메커니즘입니다. 이를 통해 복잡한 연산 및 장시간 실행되는 작업을 비동기로 수행하여 메인 스레드가 차단되는 것을 방지합니다. Web Worker를 사용하면 사용자 인터페이스의 응답성이 향상되고 애플리케이션의 성능이 강화됩니다.

Web Worker의 기본 사용법

Web Worker의 기본 사용법을 살펴보겠습니다.

자바스크립트 파일을 지정하여 새로운 Web Worker를 생성합니다.

1// worker.js (created as a separate file)
2onmessage = function(event) {
3    // Receive a message
4    const data = event.data;
5    // Perform heavy computation or processing
6    const result = data * 2;
7    // Return the result to the main thread
8    postMessage(result);
9};
 1// Main thread (main.js)
 2const worker = new Worker('worker.js');
 3
 4// Send a message to the worker
 5worker.postMessage(10);
 6
 7// Receive a message from the worker
 8worker.onmessage = function(event) {
 9    console.log('Result from Worker: ', event.data); // Result: 20
10};
11
12// Error handling
13worker.onerror = function(error) {
14    console.error('Worker error: ', error);
15};
  • 이 코드는 Web Worker를 사용하여 메인 스레드에서 연산을 요청하고 결과를 비동기적으로 받는 기본 메커니즘을 보여줍니다. 10을 메시지로 Web Worker에 보내고, 결과 메시지로 20을 받습니다. 메인 스레드와 Web Worker는 메시지를 주고받아 요청 및 처리 결과를 전달합니다.

기본 기능

Web Worker는 다음과 같은 기본 기능을 제공합니다.

  • postMessageonmessage 메인 스레드와 Web Worker 사이에서 메시지를 주고받는 데 사용됩니다. postMessage로 데이터를 보내고 onmessage로 데이터를 수신합니다.
  • 스레드 독립성 Web Worker는 메인 스레드와 완전히 독립적으로 동작합니다. 따라서 메인 스레드에서 정의된 변수와 함수에 접근할 수 없습니다. 필요한 데이터는 postMessage를 통해 전달해야 합니다.
  • 스크립트 가져오기 Web Worker 내부에서 외부 스크립트를 불러오려면 importScripts()를 사용합니다.
1// mathUtils.js
2function double(n) {
3    return n * 2;
4}
1// worker.js
2importScripts('mathUtils.js');
3
4onmessage = function(event) {
5    const input = event.data;
6    const result = double(input); // mathUtils.jsの関数を使用
7    postMessage(result);
8};
 1// Main thread (main.js)
 2const worker = new Worker('worker.js');
 3
 4// Send a message to the worker
 5worker.postMessage(10);
 6
 7// Receive a message from the worker
 8worker.onmessage = function(event) {
 9    console.log('Result from Worker: ', event.data); // Result: 20
10};
11
12// Error handling
13worker.onerror = function(error) {
14    console.error('Worker error: ', error);
15};

아래와 같이 import를 사용할 수도 있습니다.

1// lib.js
2
3// Simulated CPU-intensive task
4export function doHeavyWork(n) {
5    return n * 2;
6}
1// worker.js
2import { doHeavyWork } from './lib.js';
3
4self.onmessage = (event) => {
5  const data = event.data;
6  const result = doHeavyWork(data);
7  self.postMessage(result);
8};
 1// Main thread (main.js)
 2const worker = new Worker('worker.js', { type: 'module' });
 3
 4// Send a message to the worker
 5worker.postMessage(10);
 6
 7// Receive a message from the worker
 8worker.onmessage = function(event) {
 9    console.log('Result from Worker: ', event.data); // Result: 20
10};
11
12// Error handling
13worker.onerror = function(error) {
14    console.error('Worker error: ', error);
15};
  • new Worker의 인자에 type: 'module'을 지정하면 해당 worker를 ES 모듈로 취급하여 import 문법을 사용할 수 있습니다.

Web Workerasync 함수의 차이점

Web Worker

특징
  • 스레드 격리 Web Worker는 자바스크립트 코드를 별도의 스레드에서 실행합니다. 이를 통해 메인 스레드(UI 스레드)를 차단하지 않고도 무거운 계산 처리를 수행할 수 있습니다.
  • 동시 처리 Web Worker를 사용하면 자바스크립트 프로그램이 여러 스레드에서 동시 처리를 할 수 있습니다.
  • 메시지 통신 Web Worker와 메인 스레드는 데이터를 직접 공유하지 않고, postMessageonmessage를 사용해 메시지로 데이터를 주고받습니다.
  • UI 접근 불가 Web Worker는 DOM에 접근할 수 없으므로 UI를 직접 조작할 수 없습니다.
사용 사례
  • 이미지 처리, 데이터 분석, 암호화와 같은 무거운 계산 작업에 적합합니다.
  • 메인 스레드를 차단하지 않고 작업을 수행하고 싶을 때 사용할 수 있습니다.

async

특징
  • 비동기 처리 단순화 async 함수는 비동기 처리를 보다 읽기 쉽게 만들어 주는 문법이며, 내부적으로는 Promise를 사용합니다.
  • 단일 스레드 모든 async 함수는 메인 스레드에서 실행됩니다. 따라서 UI 스레드를 차단하지 않도록 방법을 강구해야 합니다.
  • 입출력 작업에 최적화 네트워크 통신이나 파일 읽기와 같은 비동기 작업에는 적합하지만, 복잡한 연산 작업에는 적합하지 않습니다.
사용 사례
  • API 호출과 같은 네트워크 통신에 적합합니다.
  • 브라우저 API나 Node.js를 이용한 파일 입출력 작업에 적합합니다.

주요 차이점

특징 Web Worker async
실행 환경 별도의 스레드 메인 스레드
목적 병렬 처리, 복잡한 연산의 오프로드 비동기 작업의 간결한 설명
DOM 접근 불가능 가능
통신 방법 메시지 전달 (예: postMessage) 불필요 (직접 함수 호출 또는 await를 통해 처리)
사용 사례 시간이 많이 드는 연산, 데이터 분석 I/O 작업, API 호출

각각 언제 사용할지

CPU 집약적인 작업의 경우, 메인 스레드의 부하를 줄이기 위해 Web Worker를 사용할 수 있습니다. 반면, 네트워크 통신이나 I/O 작업의 경우, async/await를 사용하여 코드를 단순화할 수 있습니다.

둘을 적절히 결합하여 효율적인 비동기 처리가 가능합니다.

Web Worker 사용 예제

무거운 연산을 오프로드하는 예시

다음은 무거운 연산을 오프로딩하는 예시입니다.

 1// worker.js
 2onmessage = function(event) {
 3    const num = event.data;
 4    const result = fibonacci(num);
 5    postMessage(result);
 6};
 7
 8function fibonacci(n) {
 9    if (n <= 1) return n;
10    return fibonacci(n - 1) + fibonacci(n - 2);
11}
1// Main thread
2const worker = new Worker('worker.js');
3
4worker.onmessage = function(event) {
5    console.log('Fibonacci result: ', event.data);
6};
7
8worker.postMessage(40); // Delegate heavy computation to the worker
  • 이 코드는 Web Worker를 사용하여 피보나치 수열 계산과 같은 무거운 작업을 메인 스레드에서 분리하여 실행하는 예시입니다.

작업 이름을 지정하는 예시입니다.

다음은 작업 이름을 지정하여 메시지를 주고받는 예시입니다.

 1// worker.js
 2self.onmessage = function(event) {
 3    switch (event.data.task) {
 4        case 'add':
 5            const result = event.data.a + event.data.b;
 6            self.postMessage({ task: 'result', value: result });
 7            break;
 8        default:
 9            self.postMessage({ task: 'error', message: 'Invalid Task' });
10    }
11};
 1// Main thread
 2const worker = new Worker('worker.js');
 3
 4worker.onmessage = function(event) {
 5    switch (event.data.task) {
 6        case 'result':
 7            console.log('Task result: ', event.data.value);
 8            break;
 9        case 'error':
10            console.error('Error: ', event.data.message);
11            break;
12    }
13};
14
15worker.postMessage({ task: 'add', a: 10, b: 20 });
  • task 이름에 따라 메시지를 분기 처리하며, Web Worker가 계산 결과 또는 오류를 작업 이름과 함께 메인 스레드로 반환하는 예시 코드입니다.

노트

Web Worker를 사용할 때는 다음 사항을 유의하세요.

  • DOM 접근 불가 Web Worker는 UI를 조작하거나 DOM에 접근할 수 없습니다. DOM 조작은 메인 스레드에서 수행해야 합니다.
  • 통신 오버헤드 메인 스레드와 Web Worker 간에 데이터를 주고받을 때 약간의 오버헤드가 발생합니다. 이는 특히 대량의 데이터를 자주 교환할 때 성능에 영향을 미칠 수 있습니다.
  • 동일 출처 정책 Web Worker 스크립트는 동일 출처 정책의 적용을 받습니다. 스크립트는 다른 도메인에서 로드될 수 없습니다.

Web Worker는 애플리케이션의 성능과 응답성을 향상시킬 수 있지만, DOM 조작 불가와 같은 제약을 고려하여 적절히 사용하는 것이 중요합니다.

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

YouTube Video