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’q — fs.promises yetarli |
| Bazaga so’rov | Yo’q — kutish oqimni to’smaydi |
| Tarmoqdan ma’lumot olish | Yo’q — await 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:
block-demo.jsni yozib ishga tushiring va hisob necha soniya davom etishini o’lchang;- O’sha faylga
setIntervalqo’shing hamda hisob paytida timer ishlamayotganini kuzating; worker.jsvamain.jsni yozib, timer endi o’z vaqtida ishlayotganini tasdiqlang;workerDataorqali boshqalimityuborib, natija o’zgarishini ko’ring;- Worker ichidan asosiy fayldagi o’zgaruvchiga murojaat qilib ko’ring va qanday xato chiqishini o’qing;
os.cpus().lengthbilan 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_threadsqo’shimcha oqim ochadi va u alohida yadroda ishlaydi.workerDataasosiy 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().lengthdan oshirilmaydi.
Keyingi darsda cluster va PM2 — dasturni bir necha yadroda ishga tushirish.