Браузер и фронтенд-рантайм
Механика web workers: dedicated, shared и OffscreenCanvas
Вы знаете, что воркеры существуют. Но 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-levelawaitи тот же граф модулей, что и остальное приложение.
Module-воркеры — современный дефолт. Classic importScripts остаётся только для легаси-кода и случаев динамической сборки имени скрипта в рантайме.
- Время жизни dedicated-воркера
- Привязан к создавшей странице
- Время жизни shared-воркера
- Последняя подписанная вкладка
- Module-воркер
- Опция { type: 'module' }
- Оверхед старта воркера
- 5–20 мс + парсинг скрипта
- Максимальный размер пула (правило)
- hardwareConcurrency − 1
OffscreenCanvas: единственная DOM-лазейка
Воркер не может тронуть DOM, но canvas — особый случай: bitmap-канвас не является частью DOM-дерева, это просто буфер пикселей. OffscreenCanvas — канвас, отвязанный от элемента <canvas>.
Паттерн:
- Главный поток вызывает
canvas.transferControlToOffscreen(), получаетOffscreenCanvas. - Передаёт его воркеру через transfer-список postMessage.
- Воркер получает 2D-контекст или WebGL-контекст и рисует целиком на своём потоке.
- Браузер отражает результат в видимый
<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 потребовало бы блокировок всего дерева — а это именно то, что модель воркеров избегает.
- 01Какие три варианта воркеров и когда какой использовать?
- 02Почему скрипт воркера должен быть same-origin (или blob-URL)?
- 03После canvas.transferControlToOffscreen(), может ли главный поток рисовать в этот canvas?
Dedicated-воркер — свежий JS-realm, event loop и OS-поток, привязанный к создавшей его странице — умирает, когда страница закрывается. Shared-воркер охватывает все вкладки origin через MessagePort-соединения, живя пока жива хоть одна вкладка. Module-воркеры используют ES-imports вместо legacy importScripts. OffscreenCanvas — единственный способ воркерам рендерить: передайте контроль canvas воркеру, и он рисует 2D или WebGL на своём потоке, браузер компонует результат — но владение эксклюзивно и передача постоянна.
встречается в41
- Federation и lookahead: батчинг за пределами DataLoadermiddle
- Senior GraphQL API: scheduling-контракт, изоляция арендаторов, наблюдаемостьsenior
- Лок и single-flight: ограничение параллельных rebuildmiddle
- Stale-while-revalidate и CDN request coalescingmiddle
- Детектирование stampede и дизайн TTL для продакшенаmiddle
- Метастабильный сбой, fencing-токены и production-постмортемыsenior
- Что такое отношение: таблицы, строки, ключи и ограниченияjunior
- Ограничения, ключи и типы данных Postgresmiddle
- JSONB, массивы и когда side table побеждаетmiddle
- Целостность схемы: deferral, версионирование и сбои в продакшнеsenior
- Где происходит data fetching — и почему это решает LCPjunior
- React Server Components и Suspense streamingmiddle
- Senior internals: RSC payload, слои кэша и production паденияsenior
- Конверт IPjunior
- Читаем IP-заголовокmiddle
- Что делает TLS и зачем он нуженjunior
- Расписание ключей, SNI, ALPN и расширенияsenior
- Защита 0-RTT, ECH, гибридный PQ и продакшн TLSsenior
- Двенадцать слоёв: один URL, семь действующих лицjunior
- Устойчивость: каскадные повторы, circuit breakers и error budgetsenior
- Что такое OpenTelemetry: API, SDK, Collector, OTLPjunior
- Сигналы OTel, Semantic Conventions и проводной формат OTLPmiddle
- Collector OTel: receivers, processors, exporters и паттерны развёртыванияmiddle
- Vendor-нейтральность, eBPF-инструментирование, Operator и OTel в браузере и serverlesssenior
- Эксплуатация OTel Collector: надёжность, version skew, режимы отказа и управлениеsenior
- Что такое trace propagation и почему сломанная propagation хуже отсутствия трейсовjunior
- traceparent и tracestate: полный формат W3C-заголовкаmiddle
- Baggage и async-границы: перенос контекста через очереди и callback''''иmiddle
- Async context на разных языках, service mesh, миграция B3 и безопасностьsenior
- Production-сбои propagation, span links и платформенный дизайнsenior
- Debugging-воронка: SLO → RED → trace → profilejunior
- Архитектура OTel: один SDK, четыре сигнала, один wire-форматmiddle
- Петля инцидента: от пейджера до постмортема до предотвращенияmiddle
- Масштаб, безопасность и ROI наблюдаемых системsenior
- At-most-once, at-least-once, exactly-once: три контракта доставкиjunior
- Consumer-side dedup: самый дешёвый путь к exactly-once processingmiddle
- Exactly-once в production: impossibility-доказательство, гибридные паттерны и реальные инцидентыsenior
- Что такое OAuth и почему пароли — не ответjunior
- Authorization code flow с PKCEmiddle
- Sender-constrained токены: DPoP и mTLSsenior
- OAuth в production: audience атаки, observability и реальные провалыsenior