← レッスン一覧に戻る レッスン 19 / 28
上級 concurrency
Wasm + Web Workers
なぜWeb Workersが必要なのか?
Wasmはデフォルトでメインスレッドで実行されます。重い計算はUIをブロックし、ボタンがフリーズしたりアニメーションが停止したりします。Web Workersを使えば、Wasmを別のスレッドで実行できます。
メインスレッド(UI) ワーカースレッド(計算)
┌──────────────┐ ┌──────────────┐
│ DOM、イベント │ postMessage │ Wasmモジュール│
│ アニメーション│ ◀──────────▶│ 重い計算処理 │
│ レスポンシブ │ │ ブロックしない│
└──────────────┘ └──────────────┘基本的なWorkerのセットアップ
worker.js
import init, { heavy_computation } from './pkg/my_wasm.js';
self.onmessage = async (e) => {
await init();
const { type, data } = e.data;
if (type === 'compute') {
const result = heavy_computation(data.iterations);
self.postMessage({ type: 'result', result });
}
};main.js
const worker = new Worker('./worker.js', { type: 'module' });
worker.onmessage = (e) => {
if (e.data.type === 'result') {
console.log('Result:', e.data.result);
}
};
// UIをブロックしない
worker.postMessage({
type: 'compute',
data: { iterations: 10_000_000 }
});複数のWorker(並列処理)
複数のWorkerに作業を分割して真の並列処理を実現します:
function createWorkerPool(size) {
const workers = [];
for (let i = 0; i < size; i++) {
workers.push(new Worker('./worker.js', { type: 'module' }));
}
return workers;
}
async function parallelPrimeCount(max, numWorkers = 4) {
const pool = createWorkerPool(numWorkers);
const chunkSize = Math.ceil(max / numWorkers);
const promises = pool.map((worker, i) => {
const start = i * chunkSize + 1;
const end = Math.min((i + 1) * chunkSize, max);
return new Promise((resolve) => {
worker.onmessage = (e) => resolve(e.data.count);
worker.postMessage({ type: 'count_primes', start, end });
});
});
const counts = await Promise.all(promises);
return counts.reduce((a, b) => a + b, 0);
}SharedArrayBuffer(上級)
コピーなしでWorker間でメモリを共有します:
// サーバーに以下のヘッダーが必要です:
// Cross-Origin-Opener-Policy: same-origin
// Cross-Origin-Embedder-Policy: require-corp
const shared = new SharedArrayBuffer(1024);
const view = new Float64Array(shared);
// メインスレッドとワーカーの両方が読み書き可能
worker.postMessage({ buffer: shared });// Rustでは共有メモリにポインタ経由でアクセス
#[wasm_bindgen]
pub fn process_shared(ptr: *mut f64, len: usize) {
let data = unsafe { std::slice::from_raw_parts_mut(ptr, len) };
for x in data.iter_mut() {
*x *= 2.0;
}
}Web Workersを使うべき場面
| ユースケース | メインスレッド | Web Worker |
|---|---|---|
| DOM操作 | ✓ | ✗(DOMアクセス不可) |
| イベントハンドラ | ✓ | ✗ |
| 重い計算 | ✗(UIがブロックされる) | ✓ |
| 画像処理 | ✗ | ✓ |
| 暗号化処理 | ✗ | ✓ |
| 物理シミュレーション | ✗ | ✓ |
| データ解析 | サイズによる | ✓(大きなファイル向け) |
パフォーマンス比較
| タスク | メインスレッド | Worker 1つ | Worker 4つ |
|---|---|---|---|
| 100万までの素数カウント | 800ms(UIフリーズ) | 800ms(UIレスポンシブ) | 200ms |
| 画像ぼかし(4K) | 120ms(UIフリーズ) | 120ms(UIレスポンシブ) | 35ms |
Workerは個々のタスクを高速化するわけではありません。UIをレスポンシブに保ち、並列処理を可能にするものです。
試してみよう
スターターコードには、メインスレッドで実行するとUIをブロックしてしまうCPU集約型の関数(三角関数の計算、素数カウント)が含まれています。本番環境では、これらをWeb Workerでラップして使用します。
試してみる
チャプタークイズ
すべての問題に正解してレッスンを完了しましょう