Node.js worker_threads: parallel hisoblash

Node.js nima darsida bitta jumla aytilgan edi: Node.js bitta oqimda ishlaydi. Asinxron kod darsida esa bu muammo emasligini ko’rdingiz — fayl yoki tarmoqni kutish oqimni band qilmaydi.

Lekin kutish bilan hisoblash boshqa-boshqa narsa. Uzoq davom etadigan sikl oqimni haqiqatan to’sadi. Buni avval ko’rsatamiz.

Muammoni ko’rsatamiz

block-demo.js faylini yarating:

function heavySum(limit) {
  let total = 0;
  for (let i = 0; i < limit; i++) {
    total += i;
  }
  return total;
}

console.log("Hisob boshlandi");
const started = Date.now();
const result = heavySum(3_000_000_000);
console.log("Natija:", result);
console.log("Ketgan vaqt (ms):", Date.now() - started);
node block-demo.js
Hisob boshlandi
Natija: 4499999997067114000
Ketgan vaqt (ms): 3025

Uch soniya davomida dastur boshqa hech narsa qila olmadi. Server bo’lganda bu uch soniya ichida kelgan har bir so’rov navbatda turadi.

Oqim nima

child_process darsidagi bola jarayondan farqi bor: worker alohida dastur emas, xuddi shu jarayon ichidagi ikkinchi oqim. Shuning uchun u yengilroq ochiladi.

Birinchi worker

Ikki fayl kerak. Avval ishchi kod — worker.js:

const { parentPort, workerData } = require("node:worker_threads");

let total = 0;
for (let i = 0; i < workerData.limit; i++) {
  total += i;
}

// Natijani asosiy oqimga qaytaramiz
parentPort.postMessage(total);

Endi asosiy fayl — main.js:

const { Worker } = require("node:worker_threads");

console.log("Asosiy oqim ishlayapti");

const worker = new Worker("./worker.js", {
  workerData: { limit: 3_000_000_000 },
});

worker.on("message", (total) => {
  console.log("Worker natijasi:", total);
});

worker.on("exit", (code) => {
  console.log("Worker tugadi, kod:", code);
});

// Asosiy oqim bo'sh: timer o'z vaqtida ishlayotganini ko'rsatamiz
let tick = 0;
const timer = setInterval(() => {
  tick += 1;
  console.log("Asosiy oqim javob beryapti:", tick);
  if (tick === 3) {
    clearInterval(timer);
  }
}, 500);
node main.js
Asosiy oqim ishlayapti
Asosiy oqim javob beryapti: 1
Asosiy oqim javob beryapti: 2
Asosiy oqim javob beryapti: 3
Worker natijasi: 4499999997067114000
Worker tugadi, kod: 0

Mana asosiy dalil. Xuddi shu uch soniyalik hisob bajarildi, lekin timer har yarim soniyada o’z vaqtida ishladi. Asosiy oqim bo’sh qoldi.

Birinchi misolda bunday bo’lmasdi: u yerda setInterval hisob tugagunicha umuman chaqirilmagan bo’lardi.

Ma’lumot qanday uzatiladi

Ikki yo’nalish bor va ikkalasi ham nomi bilan tanish:

Nima Yo’nalish Qayerda ishlatiladi
workerData Asosiy oqim → worker new Worker(...) da beriladi
parentPort.postMessage() Worker → asosiy oqim Worker ichida
worker.postMessage() Asosiy oqim → worker Ish davomida

message va exit hodisalari EventEmitter darsidagi tanish on yozuvi bilan tinglanadi.

Ikki tomonlama suhbat

Worker bir marta javob berib tugashi shart emas. counter-worker.js:

const { parentPort } = require("node:worker_threads");

parentPort.on("message", (task) => {
  if (task.command === "stop") {
    parentPort.close();
    return;
  }

  let total = 0;
  for (let i = 0; i < task.limit; i++) {
    total += i;
  }

  parentPort.postMessage({ limit: task.limit, total });
});

counter-main.js:

const { Worker } = require("node:worker_threads");

const worker = new Worker("./counter-worker.js");

worker.on("message", (answer) => {
  console.log(`${answer.limit} gacha yig'indi: ${answer.total}`);
});

worker.postMessage({ limit: 1000 });
worker.postMessage({ limit: 5000 });

// Ikki javobni olgach to'xtatamiz
setTimeout(() => {
  worker.postMessage({ command: "stop" });
}, 500);
node counter-main.js
1000 gacha yig'indi: 499500
5000 gacha yig'indi: 12497500

Worker ochiq turdi va ikkita vazifani ketma-ket bajardi. Har safar yangi worker ochish qimmat — uzoq ishlaydigan dasturda bittasini qayta ishlatish arzonroq.

Qachon worker kerak emas

Bu eng ko’p adashiladigan joy.

Vazifa Worker kerakmi
Fayl o’qish, yozish Yo’qfs.promises yetarli
Bazaga so’rov Yo’q — kutish oqimni to’smaydi
Tarmoqdan ma’lumot olish Yo’qawait yetarli
Katta massivni saralash, hisoblash Ha
Rasm yoki video qayta ishlash Ha
Shifrlash, arxivlash Ha

Nechta worker ochish kerak

Worker soni protsessor yadrolari sonidan oshsa, foyda tugaydi: oqimlar bir-birini kutib turadi.

const os = require("node:os");

console.log("Yadrolar soni:", os.cpus().length);
Yadrolar soni: 12

Bu son har kompyuterda boshqacha. Odatda worker soni yadrolar sonidan oshirilmaydi.

Amaliy mashq

node-start papkasida bajaring:

  1. block-demo.js ni yozib ishga tushiring va hisob necha soniya davom etishini o’lchang;
  2. O’sha faylga setInterval qo’shing hamda hisob paytida timer ishlamayotganini kuzating;
  3. worker.js va main.js ni yozib, timer endi o’z vaqtida ishlayotganini tasdiqlang;
  4. workerData orqali boshqa limit yuborib, natija o’zgarishini ko’ring;
  5. Worker ichidan asosiy fayldagi o’zgaruvchiga murojaat qilib ko’ring va qanday xato chiqishini o’qing;
  6. os.cpus().length bilan yadrolar sonini chiqaring.

2-qadam eng muhimi: muammoni o’z ko’zingiz bilan ko’rmaguningizcha, worker nima uchun kerakligi mavhum bo’lib qoladi.

Qisqacha xulosa

  • Kutish oqimni to’smaydi, hisoblash to’sadi — asinxronlik bu muammoni hal qilmaydi.
  • worker_threads qo’shimcha oqim ochadi va u alohida yadroda ishlaydi.
  • workerData asosiy oqimdan worker’ga, parentPort.postMessage() teskari yo’nalishda ma’lumot uzatadi.
  • Worker bilan xotira bo’linmaydi: ma’lumot nusxalanadi, funksiya uzatilmaydi.
  • worker.postMessage() bilan bitta worker’ga ketma-ket vazifa berish mumkin.
  • Fayl, baza va tarmoq uchun worker kerak emas.
  • Worker soni odatda os.cpus().length dan oshirilmaydi.

Keyingi darsda cluster va PM2 — dasturni bir necha yadroda ishga tushirish.

Bu dars foydali bo'ldimi?

marta ko'rildi