Live demo: https://iriiv.github.io/web-workers-parallel/
Интерактивный стенд для наблюдения за тем, как браузерные Web Workers распределяют CPU-тяжёлые задачи по потокам — и почему параллелизм не масштабируется линейно.
Фиксированный массив из 1 000 задач, каждая из которых выполняет 100 000 000 инкрементов (while (curr < n) curr++) — чистая нагрузка на CPU без I/O.
Инкрементальный счётчик задаёт количество Web Workers. Массив задач делится на равные части и раздаётся воркерам. Все воркеры запускаются одновременно, замеряется wall-clock время от старта до последнего завершения.
| Воркеры | Ожидание по идеалу | Реальность |
|---|---|---|
| 1 | 100% | ~baseline |
| 2 | 50% | ~55–65% |
| 4 | 25% | ~30–40% |
| 8 | 12.5% | ~20–35% |
| 12+ | ~8% | ~25–40% |
Время падает, но не в
Nраз — и уже после 4–8 воркеров почти перестаёт улучшаться.
navigator.hardwareConcurrency возвращает логические процессоры. На большинстве потребительских CPU половина из них — это Hyper-Threading (два логических потока на одном физическом ядре). Два потока, делящие одно ядро, не дают двукратного ускорения — они делят кэш, ALU и шину памяти.
Каждый new Worker() — это полноценный JS-движок: новый V8-изолят, выделение памяти, загрузка скрипта. При малом объёме задачи это время может быть сопоставимо с самой задачей.
Данные между главным потоком и воркером передаются через structured clone — копирование, а не передача по ссылке. Чем больше массив, тем дороже передача.
Любой код имеет последовательную часть (разбивка массива, создание воркеров, сбор результатов). Даже если параллельная часть идеально масштабируется, последовательная часть ставит жёсткий потолок ускорения.
Все воркеры работают в одном процессе браузера и конкурируют за:
- L2/L3 кэш — промахи кэша у одного воркера замедляют другие
- Пропускную способность RAM — память одна на всех
- Планировщик ОС — ОС решает, когда какому потоку дать квант
Браузеры ограничивают количество одновременных воркеров и могут throttle фоновые вкладки. На практике полезный диапазон — от 2 до navigator.hardwareConcurrency / 2.
Можно загрузить все потоки до 100% CPU в Диспетчере задач — оба кубика продолжат двигаться плавно. Это не баг, а фундаментальное свойство архитектуры браузера.
Web Workers выполняются в отдельных OS-потоках, полностью изолированных от потока рендеринга. Браузер внутри устроен так:
┌─────────────────────────────────────┐
│ Процесс браузера │
│ │
│ Main Thread │
│ ├─ JS (React, RAF-loop) │
│ └─ Layout / Paint │
│ │
│ Compositor Thread ← CSS-анимации │
│ │
│ Worker Thread #1 ─┐ │
│ Worker Thread #2 ├─ 100% CPU │
│ Worker Thread #N ─┘ │
└─────────────────────────────────────┘
-
CSS
@keyframes— работают на compositor thread, который вообще не знает о существовании воркеров. Никакая нагрузка на CPU через Workers его не затронет. -
requestAnimationFrameloop — работает на main thread, но поскольку Workers изолированы, main thread остаётся свободным. RAF продолжает получать кванты времени и рисует кадры без задержек.
Именно для этого и нужны Web Workers: вынести тяжёлую вычислительную работу из main thread, чтобы UI оставался отзывчивым при любой нагрузке.
Проект создан в паре с Claude Sonnet (Anthropic) через Cursor IDE — включая код, архитектурные решения и этот README.
Объяснения про параллелизм, Hyper-Threading, Закон Амдала и внутреннее устройство браузера написаны нейросетью на основе обучающих данных. Они отражают общепринятые концепции, но:
- могут содержать упрощения или неточности в деталях
- поведение конкретного браузера/ОС/железа может отличаться от описанного
- цифры в таблице ускорения — ориентировочные, а не замеренные
Воспринимай как отправную точку для изучения темы, а не как истину в последней инстанции.
- React 19 + TypeScript
- Vite 8 (сборка +
?workerимпорты) - Tailwind CSS v4
- Web Workers API
npm install
npm run devnpm run deploy