Skip to main content

Як передати дані між потоками?

1. Як влаштована взаємодія між потоками

Кожен потік (Worker) у Node.js:

  • має свій власний контекст (не ділить змінні з іншими),
  • виконується в межах одного процесу,
  • може обмінюватися повідомленнями з головним потоком через message passing (передачу повідомлень),
  • за потреби - ділити пам'ять через SharedArrayBuffer.

Тобто:

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

2. Базовий спосіб - передача повідомлень (message passing)

Це найпоширеніший і найбезпечніший спосіб обміну даними між потоками.

Приклад:

main.js

javascript
const { Worker } = require('worker_threads'); const worker = new Worker('./worker.js'); // Надсилаємо дані в потік worker.postMessage({ name: 'Tim', numbers: [1, 2, 3] }); // Отримуємо відповідь worker.on('message', (result) => { console.log('Відповідь від потоку:', result); });

worker.js

javascript
const { parentPort } = require('worker_threads'); // Отримуємо повідомлення від основного потоку parentPort.on('message', (data) => { const sum = data.numbers.reduce((a, b) => a + b, 0); parentPort.postMessage({ greeting: `Привіт, ${data.name}!`, sum }); });

Як це працює:

  • main.js створює потік і надсилає дані за допомогою postMessage().
  • Потік отримує дані через parentPort.on('message', ...).
  • Після обчислень - надсилає відповідь назад через parentPort.postMessage().

3. Передача даних при запуску - workerData

Ти можеш передати дані один раз під час створення потоку, використовуючи параметр workerData.

Приклад:

main.js

javascript
const { Worker } = require('worker_threads'); const worker = new Worker('./worker.js', { workerData: { n: 5 } }); worker.on('message', result => console.log('Результат:', result));

worker.js

javascript
const { workerData, parentPort } = require('worker_threads'); let fact = 1; for (let i = 1; i <= workerData.n; i++) fact *= i; parentPort.postMessage({ factorial: fact });

Тут дані (n: 5) передаються одразу під час створення воркера, без postMessage().

4. Спільне використання пам'яті - SharedArrayBuffer

Це просунутий спосіб, що дозволяє ділити пам'ять між потоками, щоб уникнути копіювання даних і прискорити обмін.

Використовується, коли:

  • потрібно часто обмінюватися великими масивами,
  • важлива максимальна продуктивність,
  • допустимі низькорівневі операції.

Приклад:

main.js

javascript
const { Worker } = require('worker_threads'); const shared = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT); const sharedArray = new Int32Array(shared); sharedArray[0] = 42; const worker = new Worker('./worker.js', { workerData: shared }); worker.on('exit', () => { console.log('Змінено воркером:', sharedArray[0]); // 84 });

worker.js

javascript
const { workerData } = require('worker_threads'); const arr = new Int32Array(workerData); arr[0] *= 2; // Модифікуємо спільне значення

Тут:

  • SharedArrayBuffer створює спільну область пам'яті, доступну обом потокам.
  • Зміни, зроблені в одному потоці, видно в іншому негайно.
  • Можна використовувати Atomics для безпечних операцій (щоб уникнути гонок даних).

5. Передача великих даних без копіювання - Transferable objects

Node.js підтримує передачу "за посиланням" деяких об'єктів (замість копіювання), наприклад ArrayBuffer, MessagePort, SharedArrayBuffer.

Приклад передачі великого буфера без копіювання:

javascript
const { Worker } = require('worker_threads'); const buffer = new ArrayBuffer(8); const worker = new Worker('./worker.js'); worker.postMessage(buffer, [buffer]); // передача за посиланням (buffer тепер недоступний у main) worker.on('message', () => console.log('Воркер отримав буфер!'));

worker.js

javascript
const { parentPort } = require('worker_threads'); parentPort.on('message', (buffer) => { console.log('Буфер отримано у воркері, розмір:', buffer.byteLength); parentPort.postMessage('OK'); });

Після передачі об'єкт стає "detached" (недоступний у відправника).

6. Зведення способів передачі даних

СпосібНапрямокОсобливості
postMessage() / on('message')двосторонньоНайпоширеніший спосіб, безпечний
workerDataу воркер під час створенняОдноразова передача при запуску
SharedArrayBufferспільна пам'ятьДуже швидко, але вимагає обережності
Transferable objectsодноразова передача за посиланнямБез копіювання, після передачі джерело втрачає доступ

7. Як обрати спосіб

СценарійЩо використати
Звичайні дані (об'єкти, JSON, числа)postMessage()
Передача параметрів при запускуworkerData
Обмін великою кількістю данихSharedArrayBuffer
Максимальна продуктивність, без копіюванняTransferable objects
Кілька воркерів, незалежні задачіpostMessage() + черги повідомлень

Підсумок

ХарактеристикаЗначення
Потоки ізольовані одне від одногоТак
Спільні змінні доступні?Ні
Як спілкуютьсяЧерез message passing або спільну пам'ять
Передача при стартіЧерез workerData
Розділювана пам'ятьSharedArrayBuffer + Atomics
Безпечний варіантpostMessage()

Висновок:

У Node.js потоки не діляться станом напряму.

Для обміну даними використовують:

  • повідомлення (postMessage / parentPort) - безпечний, простий спосіб;
  • спільну пам'ять (SharedArrayBuffer) - швидкий, але вимагає акуратності;
  • transferable об'єкти - для ефективної передачі великих структур.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.