Как использовать несколько потоков в JavaScript
Что проверяют
Отличаете ли вы асинхронность от параллельности: async/await и промисы не создают потоков, всё выполняется в одном event loop. Следом спрашивают, как передать в воркер большой массив без копирования и что воркеру недоступно.
Как отвечать
Код страницы крутится в одном главном потоке, и тяжёлый расчёт блокирует рендер и клики. Настоящий параллелизм в браузере дают воркеры:
- Dedicated Worker:
new Worker(new URL('./worker.js', import.meta.url), { type: 'module' }). Vite и webpack 5 сами вынесут его в отдельный чанк; - SharedWorker: один экземпляр на несколько вкладок одного origin, общение через
port; - Service Worker: не для вычислений, а для перехвата сетевых запросов, офлайна и push.
Обмен идёт через postMessage, данные копируются алгоритмом structured clone. Для больших буферов есть transfer: worker.postMessage(buf, [buf]) передаёт ArrayBuffer без копии, а у отправителя он становится пустым. Так же передаются MessagePort, ImageBitmap, OffscreenCanvas. Общая память возможна через SharedArrayBuffer и Atomics, но только на странице с cross-origin isolation: заголовки Cross-Origin-Opener-Policy: same-origin и Cross-Origin-Embedder-Policy: require-corp (или credentialless).
В воркере нет DOM, window и document, зато есть fetch, IndexedDB, WebSocket и таймеры. Ошибки ловятся через worker.onerror и messageerror, ненужный воркер останавливают worker.terminate(). Чтобы не писать протокол сообщений руками, берут Comlink: он превращает воркер в объект с асинхронными методами.
Для задач, которые режутся на куски, заводят пул по navigator.hardwareConcurrency, а не воркер на каждую задачу.
Где спотыкаются
Отвечают «запущу несколько fetch через Promise.all». Это конкурентный ввод-вывод, а не потоки: вычисления по-прежнему идут в одном.
Выносят в воркер мелкие задачи. Запуск воркера и копирование данных стоят дороже самого расчёта, выигрыш появляется на задачах от десятков миллисекунд.
Пытаются обновлять DOM из воркера. Результат нужно вернуть в главный поток сообщением, а рисовать из воркера можно разве что в переданный OffscreenCanvas.