Web Workers๋ JavaScript ์ฝ๋๋ฅผ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋์์ ์คํํ ์ ์๊ฒ ํด์ฃผ๋ Web API์ ๋๋ค. ์ด๋ฅผ ํตํด ๋ฌด๊ฑฐ์ด ์ฐ์ฐ์ ๋ณ๋ ์ค๋ ๋์์ ์ฒ๋ฆฌํ์ฌ, ๋ฉ์ธ ์ค๋ ๋(UI ์ค๋ ๋)๊ฐ ์ฐจ๋จ๋๊ฑฐ๋ ๋๋ ค์ง์ง ์๋๋ก ํฉ๋๋ค.
๋ฉ์ธ ์ค๋ ๋์ ์์ปค ์ค๋ ๋๋ ๋ฉ์์ง ํจ์ฑ(message passing) ์์คํ ์ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ผ๋ฉฐ, ๋ฐ์ดํฐ๋ ๊ณต์ ๋์ง ์๊ณ ๋ณต์ฌ๋์ด ์ ๋ฌ๋ฉ๋๋ค. ์ด๋ race condition์ ๋ฐฉ์งํฉ๋๋ค.
ํด๋น ๊ฐ๋ ์ด ํ์ํ ์ด์
- UI ๋ธ๋กํน ๋ฐฉ์ง: ๋ณต์กํ ๊ณ์ฐ, ๋์ฉ๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ, ์ด๋ฏธ์ง/๋น๋์ค ์ธ์ฝ๋ฉ ๋ฑ ๋ฌด๊ฑฐ์ด ์์ ์ ๋ฉ์ธ ์ค๋ ๋์์ ์คํํ๋ฉด UI๊ฐ ๋ฉ์ถ๊ฑฐ๋ ๋ฒ๋ฒ ๊ฑฐ๋ฆฝ๋๋ค. Web Workers๋ฅผ ์ฌ์ฉํ๋ฉด UI๊ฐ ๋๊น ์์ด ๋ถ๋๋ฝ๊ฒ ๋์ํฉ๋๋ค
- ๋ฉํฐ ์ฝ์ด ํ์ฉ: ํ๋ CPU๋ ์ฌ๋ฌ ์ฝ์ด๋ฅผ ๊ฐ์ง๊ณ ์์ง๋ง, JavaScript๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฑ๊ธ ์ค๋ ๋์ ๋๋ค. Web Workers๋ฅผ ์ฌ์ฉํ๋ฉด ์ฌ๋ฌ ์ฝ์ด๋ฅผ ํ์ฉํ์ฌ ๋ณ๋ ฌ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํฉ๋๋ค
- ์ฌ์ฉ์ ๊ฒฝํ ๊ฐ์ : ๊ธด ์์ ์ค์๋ ์ฌ์ฉ์๊ฐ ํ์ด์ง๋ฅผ ์คํฌ๋กคํ๊ฑฐ๋ ๋ฒํผ์ ํด๋ฆญํ ์ ์์ด, ๋ฐ์์ฑ์ด ๋ฐ์ด๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค ์ ์์ต๋๋ค
AS-IS: ๋ฉ์ธ ์ค๋ ๋์์ ๋ฌด๊ฑฐ์ด ์์ ์คํ
// ๋ฉ์ธ ์ค๋ ๋๊ฐ ์ฐจ๋จ๋จ
function calculatePrimes(max) {
const primes = [];
for (let i = 2; i < max; i++) {
let isPrime = true;
for (let j = 2; j < i; j++) {
if (i % j === 0) {
isPrime = false;
break;
}
}
if (isPrime) primes.push(i);
}
return primes;
}
// UI๊ฐ ๋ฉ์ถค (๊ณ์ฐ์ด ๋๋ ๋๊น์ง)
const result = calculatePrimes(100000);
console.log(result);sequenceDiagram autonumber participant User participant MainThread as Main Thread<br/>(UI + ๊ณ์ฐ) User->>MainThread: ๋ฒํผ ํด๋ฆญ (๋ฌด๊ฑฐ์ด ๊ณ์ฐ ์์) Note over MainThread: ๊ณ์ฐ ์ค...<br/>UI ๋ธ๋กํน! User->>MainThread: ์คํฌ๋กค ์๋ Note over User: โ UI ๋ฐ์ ์์ Note over MainThread: ๊ณ์ฐ ์๋ฃ MainThread->>User: ๊ฒฐ๊ณผ ํ์
TO-BE: Web Workers๋ก ๋ถ๋ฆฌ
// main.js - ๋ฉ์ธ ์ค๋ ๋
const worker = new Worker('worker.js');
worker.postMessage({ max: 100000 });
worker.onmessage = function(event) {
console.log('๊ณ์ฐ ์๋ฃ:', event.data);
};
// UI๋ ๊ณ์ ๋ฐ์ํจ// worker.js - ์์ปค ์ค๋ ๋
onmessage = function(event) {
const { max } = event.data;
const primes = calculatePrimes(max);
postMessage(primes);
};
function calculatePrimes(max) {
const primes = [];
for (let i = 2; i < max; i++) {
let isPrime = true;
for (let j = 2; j < i; j++) {
if (i % j === 0) {
isPrime = false;
break;
}
}
if (isPrime) primes.push(i);
}
return primes;
}sequenceDiagram autonumber participant User participant MainThread as Main Thread<br/>(UI) participant Worker as Worker Thread<br/>(๊ณ์ฐ) User->>MainThread: ๋ฒํผ ํด๋ฆญ MainThread->>Worker: postMessage({ max: 100000 }) Note over Worker: ๊ณ์ฐ ์ค... User->>MainThread: ์คํฌ๋กค ์๋ Note over User: โ UI ์ ์ ๋ฐ์ Note over Worker: ๊ณ์ฐ ์๋ฃ Worker->>MainThread: postMessage(result) MainThread->>User: ๊ฒฐ๊ณผ ํ์
Web Workers์ ์ข ๋ฅ
| ์ข ๋ฅ | ์ค๋ช | ์ฌ์ฉ ์ฌ๋ก |
|---|---|---|
| Dedicated Workers | ๋จ์ผ ์คํฌ๋ฆฝํธ์์๋ง ์ฌ์ฉ. ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ํํ | ํน์ ํ์ด์ง์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์์ |
| Shared Workers | ์ฌ๋ฌ ์ฐฝ, ํญ, iframe์์ ๊ณต์ ๊ฐ๋ฅ. ๊ฐ์ ๋๋ฉ์ธ ํ์ | ์ฌ๋ฌ ํญ ๊ฐ ๋ฐ์ดํฐ ๊ณต์ , ์ค์ ์ง์ค์ ์ฒ๋ฆฌ |
| Service Workers | ์ฑ-๋ธ๋ผ์ฐ์ -๋คํธ์ํฌ ์ฌ์ด์ ํ๋ก์ ์ญํ . ์คํ๋ผ์ธ ์ง์, ํธ์ ์๋ฆผ | PWA, ์คํ๋ผ์ธ ๊ฒฝํ, ๋ฐฑ๊ทธ๋ผ์ด๋ ๋๊ธฐํ |
Dedicated Worker ์ฌ์ฉ๋ฒ
Worker ์์ฑ ๋ฐ ๋ฉ์์ง ์ ์ก
๋ฉ์ธ ์ค๋ ๋ (main.js)
// 1. Worker ์์ฑ
const worker = new Worker('worker.js');
// 2. ์์ปค์๊ฒ ๋ฉ์์ง ์ ์ก
worker.postMessage({
command: 'start',
data: [1, 2, 3, 4, 5]
});
// 3. ์์ปค๋ก๋ถํฐ ๋ฉ์์ง ์์
worker.onmessage = function(event) {
console.log('์์ปค ๊ฒฐ๊ณผ:', event.data);
// event.data = { status: 'complete', result: 15 }
};
// 4. ์๋ฌ ์ฒ๋ฆฌ
worker.onerror = function(error) {
console.error('์์ปค ์๋ฌ:', error.message);
console.error('ํ์ผ:', error.filename);
console.error('๋ผ์ธ:', error.lineno);
};
// 5. ์์ปค ์ข
๋ฃ
worker.terminate();์์ปค ์ค๋ ๋ (worker.js)
// ๋ฉ์ธ ์ค๋ ๋๋ก๋ถํฐ ๋ฉ์์ง ์์
onmessage = function(event) {
const { command, data } = event.data;
if (command === 'start') {
// ๋ฌด๊ฑฐ์ด ์์
์ํ
const sum = data.reduce((acc, val) => acc + val, 0);
// ๋ฉ์ธ ์ค๋ ๋๋ก ๊ฒฐ๊ณผ ์ ์ก
postMessage({
status: 'complete',
result: sum
});
}
};
// ์์ปค ์์ฒด ์ข
๋ฃ
// close();addEventListener๋ฅผ ์ฌ์ฉํ ๋ฐฉ๋ฒ
// ๋ฉ์ธ ์ค๋ ๋
const worker = new Worker('worker.js');
worker.addEventListener('message', function(event) {
console.log('๋ฉ์์ง ์์ :', event.data);
});
worker.addEventListener('error', function(error) {
console.error('์๋ฌ ๋ฐ์:', error);
});
worker.postMessage('Hello Worker');// ์์ปค ์ค๋ ๋
addEventListener('message', function(event) {
console.log('๋ฉ์ธ ์ค๋ ๋๋ก๋ถํฐ:', event.data);
postMessage('Hello Main Thread');
});Worker์ ์ฃผ์ ํน์ง 4๊ฐ์ง
1. ๋ฉ์์ง ํจ์ฑ ์์คํ
๋ฉ์ธ ์ค๋ ๋์ ์์ปค ์ค๋ ๋๋ ์ง์ ์ ์ธ ๋ฐ์ดํฐ ๊ณต์ ์์ด postMessage()์ onmessage๋ฅผ ํตํด ํต์ ํฉ๋๋ค. ๋ฐ์ดํฐ๋ ๋ณต์ฌ๋์ด ์ ๋ฌ๋๋ฏ๋ก race condition์ด ๋ฐ์ํ์ง ์์ต๋๋ค.
// ๋ฉ์ธ ์ค๋ ๋
const data = { numbers: [1, 2, 3] };
worker.postMessage(data);
// ์๋ณธ ์์ ํด๋ ์์ปค์๊ฒ ์ํฅ ์์
data.numbers.push(4);// ์์ปค ์ค๋ ๋
onmessage = function(event) {
const receivedData = event.data;
// receivedData.numbers = [1, 2, 3] (๋ณต์ฌ๋ณธ)
receivedData.numbers.push(10);
postMessage(receivedData);
};๋ฉ์์ง ํ๋ฆ:
Main Thread Worker Thread
| |
|--- postMessage() ----->|
| onmessage
| [Process]
|<-- postMessage() -------|
onmessage |
2. Worker Scope์ ์ ํ ์ฌํญ
Worker๋ ๋ณ๋์ ์ ์ญ ์ค์ฝํ๋ฅผ ๊ฐ์ง๋ฉฐ, ๋ฉ์ธ ์ค๋ ๋์ ์ผ๋ถ API์ ์ ๊ทผํ ์ ์์ต๋๋ค.
โ Worker์์ ์ฌ์ฉ ๋ถ๊ฐ๋ฅํ ๊ฒ
- DOM ์กฐ์:
document,window,parent์ ๊ทผ ๋ถ๊ฐ - DOM API:
alert(),confirm(),prompt() - ์ผ๋ถ Window ๋ฉ์๋/์์ฑ
// โ Worker์์ ๋ถ๊ฐ๋ฅ
onmessage = function(event) {
document.getElementById('result').textContent = 'Done'; // ์๋ฌ!
alert('Finished'); // ์๋ฌ!
};โ Worker์์ ์ฌ์ฉ ๊ฐ๋ฅํ ๊ฒ
- ํ์ค JavaScript:
String,Array,Object,JSON,Math - ํ์ด๋จธ:
setTimeout(),setInterval() - ๋คํธ์ํฌ ์์ฒญ:
fetch(),XMLHttpRequest - WebSockets
- importScripts(): ์ธ๋ถ ์คํฌ๋ฆฝํธ ๋ก๋ฉ
// โ
Worker์์ ๊ฐ๋ฅ
onmessage = async function(event) {
// fetch ์ฌ์ฉ ๊ฐ๋ฅ
const response = await fetch('https://api.example.com/data');
const data = await response.json();
// JSON ์ฒ๋ฆฌ ๊ฐ๋ฅ
const processed = JSON.stringify(data);
postMessage(processed);
};Worker Global Scope ์ข ๋ฅ
| Scope | ์ค๋ช |
|---|---|
| DedicatedWorkerGlobalScope | Dedicated Worker์ ์ ์ญ ์ค์ฝํ |
| SharedWorkerGlobalScope | Shared Worker์ ์ ์ญ ์ค์ฝํ |
| ServiceWorkerGlobalScope | Service Worker์ ์ ์ญ ์ค์ฝํ |
| WorkerGlobalScope | ๋ชจ๋ ์์ปค๊ฐ ์์ํ๋ ๊ธฐ๋ณธ ์ค์ฝํ |
3. importScripts()๋ก ์ธ๋ถ ์คํฌ๋ฆฝํธ ๋ก๋ฉ
Worker ๋ด๋ถ์์ ์ฌ๋ฌ JavaScript ํ์ผ์ ๋ก๋ํ ์ ์์ต๋๋ค.
// worker.js
importScripts('utils.js', 'math-helpers.js', 'data-processor.js');
// ์ด์ ๋ก๋๋ ํ์ผ์ ํจ์๋ค์ ์ฌ์ฉ ๊ฐ๋ฅ
onmessage = function(event) {
const result = calculateSum(event.data); // utils.js์์ ์ ์
postMessage(result);
};ํน์ง:
- ๋๊ธฐ์ ์ผ๋ก ๋ก๋๋จ (์์๋๋ก ์คํ)
- ์ฌ๋ฌ ํ์ผ์ ํ ๋ฒ์ ๋ก๋ ๊ฐ๋ฅ
- ๋ก๋ ์คํจ ์ ์์ปค๊ฐ ์ข ๋ฃ๋จ
4. ์์ปค์์ ์์ปค ์์ฑ ๊ฐ๋ฅ
Worker๋ ์๋ก์ด ์์ปค๋ฅผ ์์ฑํ ์ ์์ต๋๋ค. ๋จ, **๊ฐ์ origin(์ถ์ฒ)**์ ์์ด์ผ ํฉ๋๋ค.
// worker.js
onmessage = function(event) {
// ์๋ธ ์์ปค ์์ฑ
const subWorker = new Worker('sub-worker.js');
subWorker.postMessage(event.data);
subWorker.onmessage = function(subEvent) {
// ๋ฉ์ธ ์ค๋ ๋๋ก ๊ฒฐ๊ณผ ์ ๋ฌ
postMessage(subEvent.data);
};
};์ค์ ์ฌ์ฉ ์ฌ๋ก
1. ์ด๋ฏธ์ง ์ฒ๋ฆฌ
// main.js
const worker = new Worker('image-processor.js');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// ์ด๋ฏธ์ง ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
worker.postMessage({
command: 'process',
imageData: imageData
});
worker.onmessage = function(event) {
// ์ฒ๋ฆฌ๋ ์ด๋ฏธ์ง ๋ฐ์ดํฐ๋ก ์บ๋ฒ์ค ์
๋ฐ์ดํธ
ctx.putImageData(event.data.imageData, 0, 0);
};// image-processor.js
onmessage = function(event) {
const { command, imageData } = event.data;
if (command === 'process') {
const data = imageData.data;
// ๊ทธ๋ ์ด์ค์ผ์ผ ๋ณํ (๋ฌด๊ฑฐ์ด ์์
)
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = avg; // Red
data[i + 1] = avg; // Green
data[i + 2] = avg; // Blue
}
postMessage({ imageData: imageData });
}
};2. ๋์ฉ๋ JSON ํ์ฑ
// main.js
const worker = new Worker('json-parser.js');
fetch('large-data.json')
.then(response => response.text())
.then(jsonString => {
worker.postMessage({ json: jsonString });
});
worker.onmessage = function(event) {
const parsedData = event.data;
console.log('ํ์ฑ ์๋ฃ:', parsedData);
updateUI(parsedData);
};// json-parser.js
onmessage = function(event) {
const { json } = event.data;
// ๋์ฉ๋ JSON ํ์ฑ (๋ฉ์ธ ์ค๋ ๋ ๋ธ๋กํน ์์)
const parsed = JSON.parse(json);
// ์ถ๊ฐ ์ฒ๋ฆฌ
const processed = parsed.items.map(item => ({
id: item.id,
name: item.name,
processed: true
}));
postMessage(processed);
};3. ์ค์๊ฐ ๋ฐ์ดํฐ ๋ถ์
// main.js
const worker = new Worker('analytics.js');
// ์ฃผ๊ธฐ์ ์ผ๋ก ๋ฐ์ดํฐ ์ ์ก
setInterval(() => {
const sensorData = readSensorData();
worker.postMessage({ data: sensorData });
}, 100);
worker.onmessage = function(event) {
const { analysis } = event.data;
updateDashboard(analysis);
};// analytics.js
let dataBuffer = [];
onmessage = function(event) {
dataBuffer.push(event.data.data);
if (dataBuffer.length >= 10) {
// ํต๊ณ ๋ถ์
const avg = dataBuffer.reduce((a, b) => a + b, 0) / dataBuffer.length;
const max = Math.max(...dataBuffer);
const min = Math.min(...dataBuffer);
postMessage({
analysis: { avg, max, min }
});
dataBuffer = [];
}
};4. ์ํธํ/๋ณตํธํ
// main.js
const worker = new Worker('crypto-worker.js');
worker.postMessage({
command: 'encrypt',
text: 'Sensitive data',
key: 'secret-key'
});
worker.onmessage = function(event) {
console.log('์ํธํ ์๋ฃ:', event.data.encrypted);
};// crypto-worker.js
importScripts('crypto-lib.js'); // ์ธ๋ถ ์ํธํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
onmessage = function(event) {
const { command, text, key } = event.data;
if (command === 'encrypt') {
const encrypted = performEncryption(text, key);
postMessage({ encrypted: encrypted });
}
};์๋ฌ ์ฒ๋ฆฌ ๋ฐ ๋๋ฒ๊น
์๋ฌ ํธ๋ค๋ฌ
// ๋ฉ์ธ ์ค๋ ๋
worker.onerror = function(error) {
console.error('์์ปค ์๋ฌ ๋ฐ์');
console.error('๋ฉ์์ง:', error.message);
console.error('ํ์ผ:', error.filename);
console.error('๋ผ์ธ ๋ฒํธ:', error.lineno);
// ์๋ฌ ๋ณต๊ตฌ ๋ก์ง
worker.terminate();
worker = new Worker('worker.js'); // ์ฌ์์
};Worker ์ข ๋ฃ
// ๋ฉ์ธ ์ค๋ ๋์์ ์ข
๋ฃ
worker.terminate();
console.log('์์ปค ์ข
๋ฃ๋จ');
// ์์ปค๊ฐ ์ข
๋ฃ๋๋ฉด ๋ ์ด์ ๋ฉ์์ง๋ฅผ ๋ฐ์ ์ ์์// ์์ปค ์ค๋ ๋ ๋ด๋ถ์์ ์์ฒด ์ข
๋ฃ
onmessage = function(event) {
if (event.data.command === 'shutdown') {
close(); // ์์ปค ์์ฒด ์ข
๋ฃ
}
};