awesome-everything EN
↑ Обратно к восхождению

Браузер и фронтенд-рантайм

Механика web workers: dedicated, shared и OffscreenCanvas

Суть Dedicated vs shared vs module-воркеры, лазейка OffscreenCanvas и почему URL скрипта воркера должен быть same-origin.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 14 min

Вы знаете, что воркеры существуют. Но new Worker('script.js') создаёт dedicated-поток, умирающий с вкладкой — что делать, если пяти вкладкам нужно одно WebSocket-соединение, или WebGL-рендеринг должен пережить перегрузку главного потока?

Dedicated-воркеры

new Worker('worker.js') поднимает свежий JS-realm на своём OS-потоке, со своим event loop, своим глобалом (DedicatedWorkerGlobalScope) и без доступа к document, window или DOM. Он может использовать fetch, IndexedDB, WebSocket, OffscreenCanvas, таймеры, importScripts/ES-модули. Коммуникация — исключительно postMessage в обе стороны.

Воркер привязан к создавшей его странице: закройте вкладку — и воркер умирает. Используйте его для всего CPU-bound, что иначе пробило бы бюджет задачи 50 мс — парсинг больших JSON-payload-ов, фильтры изображений, криптографическое хеширование, диффинг, сжатие, подсветка синтаксиса, физика.

Ограничение same-origin. URL скрипта воркера должен быть same-origin (или blob-URL). Нельзя загрузить воркер прямо со стороннего CDN, не проксировав его через свой origin. Это та же origin-граница, что защищает остальную модель безопасности браузера.

Shared-воркеры

new SharedWorker('s.js') создаёт воркер, разделяемый между всеми вкладками одного origin. Каждая вкладка коннектится через MessagePort, и воркер живёт, пока жива хоть одна вкладка — полезно для одного общего WebSocket-соединения или согласованного кеша между вкладками. Поддержка слабее, чем у dedicated-воркеров, и отладка тяжелее, потому что время жизни воркера отвязано от любой отдельной вкладки.

Module-воркеры

Опция { type: 'module' } меняет способ загрузки зависимостей:

  • Classic-воркер — использует importScripts() для синхронной загрузки зависимостей.
  • Module-воркер — использует обычный статический import. Это даёт tree-shaking, top-level await и тот же граф модулей, что и остальное приложение.

Module-воркеры — современный дефолт. Classic importScripts остаётся только для легаси-кода и случаев динамической сборки имени скрипта в рантайме.

Сравнение вариантов воркеров
Время жизни dedicated-воркера
Привязан к создавшей странице
Время жизни shared-воркера
Последняя подписанная вкладка
Module-воркер
Опция { type: 'module' }
Оверхед старта воркера
5–20 мс + парсинг скрипта
Максимальный размер пула (правило)
hardwareConcurrency − 1

OffscreenCanvas: единственная DOM-лазейка

Воркер не может тронуть DOM, но canvas — особый случай: bitmap-канвас не является частью DOM-дерева, это просто буфер пикселей. OffscreenCanvas — канвас, отвязанный от элемента <canvas>.

Паттерн:

  1. Главный поток вызывает canvas.transferControlToOffscreen(), получает OffscreenCanvas.
  2. Передаёт его воркеру через transfer-список postMessage.
  3. Воркер получает 2D-контекст или WebGL-контекст и рисует целиком на своём потоке.
  4. Браузер отражает результат в видимый <canvas> — без участия главного потока.

Это делает возможными плавные 60 fps WebGL-визуализации, рендеринг карт и игры даже когда главный поток забит.

Воркер также может создать standalone new OffscreenCanvas(w, h) чисто для off-screen рендеринга — генерации текстур, обработки изображений — и вернуть результат как transferable ImageBitmap.

Ограничение исключительности. OffscreenCanvas, чей контроль был передан, нельзя также рисовать из главного потока — владение эксклюзивно, ровно как у переданного ArrayBuffer. Если оба потока должны рисовать, нужны два canvas или другая архитектура; платформа намеренно запрещает двум потокам гонку на одном bitmap.

Викторина

Пяти вкладкам браузера одного origin нужно одно общее WebSocket-соединение, управляемое в одном месте. Какой тип воркера подходит?

Викторина

После `canvas.transferControlToOffscreen()` главный поток вызывает `ctx.fillRect(0,0,100,100)`. Что происходит?

Закончи аналогию

Воркер, разделяемый между всеми вкладками одного origin — каждая вкладка коннектится через отдельный порт — называется как?

Почему это работает

Почему OffscreenCanvas существует, если воркеры не могут трогать DOM? Canvas особый, потому что его вывод — плоский bitmap — у него нет DOM-событий, нет CSS, нет дерева доступности. Пайплайн рендеринга браузера читает bitmap и компонует его; ничему остальному в DOM-дереве не важно, кто писал пиксели. Это позволяет безопасно отдать запись пикселей любому потоку. Обобщить это на весь DOM потребовало бы блокировок всего дерева — а это именно то, что модель воркеров избегает.

Вспомните перед уходом
  1. 01
    Какие три варианта воркеров и когда какой использовать?
  2. 02
    Почему скрипт воркера должен быть same-origin (или blob-URL)?
  3. 03
    После canvas.transferControlToOffscreen(), может ли главный поток рисовать в этот canvas?
Итог

Dedicated-воркер — свежий JS-realm, event loop и OS-поток, привязанный к создавшей его странице — умирает, когда страница закрывается. Shared-воркер охватывает все вкладки origin через MessagePort-соединения, живя пока жива хоть одна вкладка. Module-воркеры используют ES-imports вместо legacy importScripts. OffscreenCanvas — единственный способ воркерам рендерить: передайте контроль canvas воркеру, и он рисует 2D или WebGL на своём потоке, браузер компонует результат — но владение эксклюзивно и передача постоянна.

Связанные уроки
встречается в41
Продолжить восхождение ↑Structured clone и transferables
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.