Web Workers и многопоточность

Web Workers предоставляют возможность выполнения JavaScript-кода в фоновом потоке, отдельно от основного потока интерфейса. Это позволяет выполнять тяжёлые вычисления, не блокируя рендеринг страницы и обеспечивая плавность пользовательского опыта. Основная концепция заключается в том, что основной поток и воркеры работают в изоляции, а взаимодействие происходит через обмен сообщениями.

Создание воркера осуществляется с помощью конструктора Worker:

const worker = new Worker('worker.js');

Внутри файла worker.js размещается код, который будет выполняться в отдельном потоке. Для обмена данными используется метод postMessage и обработчик события message:

worker.js:

self.onmess age = function(event) {
    const result = event.data * 2;
    self.postMessage(result);
};

main.js:

worker.postMessage(10);

worker.onmess age = function(event) {
    console.log('Результат из воркера:', event.data);
};

Ключевой особенностью является то, что воркер не имеет доступа к DOM, что требует передачи данных через сообщения.


Типы Web Workers

Существует несколько типов воркеров:

  1. Dedicated Worker — один воркер на один скрипт. Используется чаще всего.
  2. Shared Worker — может использоваться несколькими скриптами или окнами одного сайта. Поддержка браузерами ограничена.
  3. Service Worker — специализированный воркер, позволяющий перехватывать сетевые запросы, кэшировать ресурсы и работать оффлайн. Service Worker работает вне контекста DOM и активируется по событиям install и activate.

Параллелизм и взаимодействие с Preact

В Preact, как и в React, интерфейс обновляется в основном потоке. Если необходимо выполнять тяжёлые вычисления (например, обработку больших массивов данных, фильтрацию или рендеринг сложных графиков), Web Workers позволяют вынести эти операции из UI-потока.

Пример использования Web Worker с Preact:

import { h, render, Component } from 'preact';

class HeavyComputation extends Component {
    state = { result: null };

    componentDidMount() {
        this.worker = new Worker('./worker.js');
        this.worker.postMessage([1, 2, 3, 4, 5]);

        this.worker.onmess age = (event) => {
            this.setState({ result: event.data });
        };
    }

    componentWillUnmount() {
        this.worker.terminate();
    }

    render({}, { result }) {
        return <div>Результат: {result}</div>;
    }
}

render(<HeavyComputation />, document.body);

В этом примере вычисления выполняются в отдельном потоке, а Preact отвечает только за рендеринг обновлённого состояния, что обеспечивает плавный интерфейс без зависаний.


Передача данных между воркером и основным потоком

Обмен данными осуществляется через структурное клонирование. Это значит, что объекты и массивы копируются, а не передаются по ссылке. Для больших массивов рекомендуется использовать Transferable Objects, например ArrayBuffer, чтобы избежать копирования данных:

const buffer = new ArrayBuffer(1024);
worker.postMessage(buffer, [buffer]);

После передачи буфера он становится недоступным в основном потоке, что снижает нагрузку на память и ускоряет передачу.


Паттерны использования Web Workers

  • Декомпозиция задач: разбиение тяжёлых вычислений на несколько воркеров.
  • Пул воркеров (Worker Pool): создание ограниченного числа воркеров для обработки очереди задач.
  • Комбинированные подходы с Preact Signals: использование реактивного состояния для обновления UI по мере получения промежуточных результатов.

Пример реализации простого пула воркеров:

class WorkerPool {
    constructor(size, script) {
        this.workers = Array.from({ length: size }, () => new Worker(script));
        this.queue = [];
    }

    runTask(data) {
        return new Promise((resolve) => {
            const worker = this.workers.shift();
            worker.onmess age = (event) => {
                resolve(event.data);
                this.workers.push(worker);
                if (this.queue.length) {
                    const next = this.queue.shift();
                    this.runTask(next.data).then(next.resolve);
                }
            };
            if (worker) {
                worker.postMessage(data);
            } else {
                this.queue.push({ data, resolve });
            }
        });
    }
}

Ограничения и особенности

  • Воркеры не имеют прямого доступа к DOM и Window API, за исключением ограниченного набора функций (например, setTimeout, fetch).
  • Использование большого числа воркеров может привести к высокой нагрузке на CPU.
  • Необходимо учитывать стоимость сериализации данных при обмене сообщениями между потоками.
  • Debugging требует отдельной консоли для воркеров или специальных инструментов в браузерах.

Интеграция с современными инструментами

В Preact-приложениях часто используется Vite или Webpack для работы с воркерами через import-спецификаторы:

import Worker from './worker.js?worker';

const worker = new Worker();

Это позволяет автоматически обрабатывать скрипты воркеров, упрощает сборку и поддержку модулей.


Использование Web Workers в Preact обеспечивает высокую отзывчивость интерфейса при интенсивных вычислениях, позволяет создавать сложные приложения с параллельной обработкой данных и обеспечивает современный подход к многопоточности в браузере.