IlmHamroh
JavaScript Full-stack/24-qism. Node.js37/55-dars18 daqiqa
Mundarija (28)

Node.js worker pool: Piscina bilan worker'larni qayta ishlatish

Qisqacha: Har vazifaga yangi worker ochish qimmat (~20 ms). Worker pool (hovuz) worker'larni bir marta ochadi va navbatdagi vazifalarni bo'shaganlariga beradi. Kanon o'lchovida 16 vazifa har biriga yangi Worker bilan 102 ms, Piscina pool'i bilan 39 ms oldi (issiq). Piscina — Node'dagi eng ko'p ishlatiladigan pool: pool.run(ma'lumot) Promise qaytaradi, navbat, chegaralar va bekor qilishni o'zi boshqaradi.

Bu darsda

  • Pool nima uchun kerakligini ishga tushish narxi bilan tushuntira olasiz.
  • 40 qatorlik o'z mini-pool'ingizni yozib, pool ichini ko'rasiz.
  • Piscina bilan vazifa yuborasiz, nomli funksiya chaqirasiz, xatoni ushlaysiz.
  • Pool hajmi, maxQueue va AbortSignal ni to'g'ri sozlaysiz.
  • Piscina va tinypool orasidan tanlaysiz va valyuta ga nega kirmaganini bilasiz.

Oldin bilishingiz kerak: worker_threads, Asinxron naqshlar (parallellik chegarasi), O'z npm paketingizni nashr qilish (paket o'rnatish).

1. Nega bu kerak?

Oldingi darsda worker event loop'ni bo'shatishini ko'rdik. Lekin narxini ham o'lchadik: bitta bo'sh worker 20 ms da ishga tushadi. Har worker — alohida V8: o'z heap'i, o'z event loop'i, modullarni qaytadan yuklaydi.

Endi tasavvur qiling: Telegram bot foydalanuvchilar yuborgan rasmlarning kichik nusxasini (thumbnail) yasaydi. Kuniga 10 000 ta rasm, har biri ~30 ms hisob. Har rasmga yangi worker ochilsa — 30 ms ish + 20 ms ishga tushish. Vaqtning 40 % i foydasiz ketadi. Bir paytda 500 ta rasm kelsa, 500 ta worker ochiladi va xotira tugaydi.

Yechim — worker'larni qayta ishlatish. Bir marta 4–8 ta worker ochiladi, vazifalar navbatga qo'yiladi, bo'shagan worker navbatdagi vazifani oladi. Bu pool (hovuz) — Asinxron naqshlar darsidagi "parallellik chegarasi" g'oyasining thread versiyasi.

Hayotiy o'xshatish: taksi bekati. Har yo'lovchi uchun yangi mashina sotib olish (yangi worker) — aql bovar qilmas darajada qimmat. Bekatda 5 ta taksi turadi. Yo'lovchi kelsa, bo'sh taksiga o'tiradi. Hammasi band bo'lsa, navbatda kutadi. Taksi yo'lovchini tashlab, bekatga qaytadi va keyingisini oladi.

2. Pool ichida nima bor: o'z mini-pool'imiz

Kutubxonaga o'tishdan oldin pool'ni o'zimiz yozamiz — ichida hech qanday sehr yo'qligini ko'rish uchun. Ikki ro'yxat kerak: bo'sh worker'lar va kutayotgan vazifalar. #tarqat() ularni juftlaydi:

js
import {
  Worker, isMainThread, parentPort,
} from "node:worker_threads";

const fib = (n) => (n < 2 ? n : fib(n - 1) + fib(n - 2));

class MiniPool {
  #bosh = [];     // bo'sh worker'lar
  #navbat = [];   // { n, ok } — worker kutayotgan vazifalar
  #hammasi = [];
  constructor(hajm) {
    for (let i = 0; i < hajm; i++) {
      const w = new Worker(new URL(import.meta.url));
      this.#hammasi.push(w);
      this.#bosh.push(w);
    }
  }
  run(n) {
    return new Promise((ok) => {
      this.#navbat.push({ n, ok });
      this.#tarqat();
    });
  }
  #tarqat() {
    while (this.#bosh.length && this.#navbat.length) {
      const w = this.#bosh.pop();
      const { n, ok } = this.#navbat.shift();
      w.once("message", (natija) => {
        ok(natija);
        this.#bosh.push(w); // worker qaytdi — qayta ishlatiladi
        this.#tarqat();
      });
      w.postMessage(n);
    }
  }
  get navbatda() { return this.#navbat.length; }
  yop() {
    return Promise.all(this.#hammasi.map((w) => w.terminate()));
  }
}

if (isMainThread) {
  const pool = new MiniPool(2);
  const ishlar = [20, 21, 22, 23, 24].map((n) => pool.run(n));
  console.log("navbatda:", pool.navbatda);
  console.log(await Promise.all(ishlar));
  await pool.yop();
} else {
  parentPort.on("message", (n) => parentPort.postMessage(fib(n)));
}

Konsolda:

text
navbatda: 3
[ 6765, 10946, 17711, 28657, 46368 ]

5 ta vazifa, 2 ta worker. Ikkitasi darhol worker'ga ketdi, uchtasi navbatda qoldi. Har worker natija qaytargach, #bosh ga qaytadi va #tarqat() navbatdan keyingisini beradi. Worker ichida parentPort.on("message") — u bitta vazifadan keyin tugamaydi, keyingisini kutadi. Natijalar tartibi saqlandi, chunki Promise.all har Promise'ni o'z joyida kutadi.

Shu jarayon qadamma-qadam:

Qadam Nima bo'ldi Navbat Konsolda
1 run(20), run(21) — ikkala worker'ga ketdi bo'sh —
2 run(22), run(23), run(24) — bo'sh worker yo'q 22, 23, 24 —
3 sinxron kod davom etdi 22, 23, 24 navbatda: 3
4 birinchi bo'shagan worker 22 ni oldi 23, 24 —
5 keyingi bo'shagan worker 23 ni oldi 24 —
6 yana bittasi bo'shadi va 24 ni oldi bo'sh —
7 5 ta natija tayyor, Promise.all bajarildi bo'sh [ 6765, … ]

Qaysi worker birinchi bo'shashi oldindan ma'lum emas, lekin natijalar massividagi tartib baribir o'zgarmaydi.

Quyidagi diagrammada vazifa yo'liga qarang:

flowchart LR
  A["pool.run(n)"] --> Q["Navbat"]
  Q --> T{"Bo'sh worker<br/>bormi?"}
  T -- ha --> W["Worker<br/>hisoblaydi"]
  T -- yo'q --> Q
  W -- natija --> R["Promise bajariladi"]
  W -- "bo'shadi" --> T

Mini-pool'imizda nima yetishmaydi? Juda ko'p narsa: worker xatoda yiqilsa, uning vazifasi abadiy kutadi va worker o'rniga yangisi ochilmaydi. Navbat cheksiz o'sadi. Bekor qilish yo'q. Har vazifa — bitta funksiya. Bularning hammasini o'zingiz yozish — haftalar ishi va xatolar manbai. Shuning uchun sinalgan kutubxona olinadi.

3. Piscina

3.1 O'rnatish va narx

Piscina (ispancha "basseyn") — Node.js jamoasi a'zolari yozgan worker pool kutubxonasi. 2026-10 holatiga oxirgi versiya — 5.3.2. Kanon uni alohida papkada o'rnatib o'lchadi (t37-ornatish.txt):

bash
npm i --save-exact piscina@5.3.2
text
added 3 packages, and audited 4 packages in 3s
piscina: 3 paket, 0.8 MB, 157 fayl

3 paket: piscina ning o'zi va @napi-rs/nice — thread ustuvorligini (priority) sozlash uchun kichik native modul (Windows'da nice-win32-x64-msvc). Paketni o'rnatish va versiyani qat'iy yozish (--save-exact) sizga paket nashri darsidan tanish.

3.2 Worker fayli va run

Piscina'da worker — oddiy modul fayli. U funksiya eksport qiladi, Piscina o'zi parentPort, xabarlar va javobni boshqaradi:

js
// ish.mjs — worker fayli
const fib = (n) => (n < 2 ? n : fib(n - 1) + fib(n - 2));

export default function ({ n }) {
  if (n < 0) throw new Error("n manfiy bo'lmasin");
  return fib(n);
}

export function kvadrat(x) {
  return x * x;
}

Asosiy fayl pool yaratadi va vazifa yuboradi:

js
import { Piscina } from "piscina";
import { availableParallelism } from "node:os";

const pool = new Piscina({
  filename: new URL("./ish.mjs", import.meta.url).href,
});
console.log("yadrolar:", availableParallelism());
const { minThreads, maxThreads } = pool.options;
console.log("min/max:", minThreads, maxThreads);
console.log("fib(25):", await pool.run({ n: 25 }));
console.log("kvadrat:", await pool.run(7, { name: "kvadrat" }));
try {
  await pool.run({ n: -1 });
} catch (xato) {
  console.log("xato:", xato.message);
}
console.log("bajarildi:", pool.completed);
console.log("thread:", pool.threads.length);
await pool.destroy();

Biz ishga tushirdik (Node 24.21.0, piscina 5.3.2, i5-12500H — 16 mantiqiy yadro):

text
yadrolar: 16
min/max: 8 24
fib(25): 75025
kvadrat: 49
xato: n manfiy bo'lmasin
bajarildi: 3
thread: 8

Bu bloklar saytda va zavod tekshiruvchisida ishga tushmaydi — Piscina tashqi paket, uni o'z loyihangizga o'rnatasiz.

Qatorma-qator:

Qism Nima qiladi
filename: ...href worker fayli — to'liq file:// URL (nisbiy yo'l emas)
pool.run(ma'lumot) vazifani navbatga qo'yadi, Promise qaytaradi
{ name: "kvadrat" } default emas, nomli eksport chaqiriladi
worker'dagi throw run Promise'i shu xato bilan rad etiladi
pool.destroy() hamma worker'ni yopadi — aks holda dastur tugamaydi

Sukut bo'yicha Piscina yadrolar sonining yarmicha worker'ni oldindan ochadi (minThreads: 16 dan 8) va yuk ko'payganda 1.5 baravarigacha (maxThreads: 24) oshiradi. availableParallelism() — Node'ning "nechta parallel ish mumkin" degan bahosi, ko'pincha mantiqiy yadrolar soni.

Worker'dagi xato asosiy thread'ga keldi, lekin worker o'lmadi — Piscina uni pool'da qoldirdi. Oldingi darsdagi qo'lda o'ram bilan solishtiring: u yerda har xato uchun error va exit ni tinglash kerak edi.

4. O'lchov: pool qancha tejaydi

4.1 Kanon: yangi Worker va pool

Kanon 16 ta vazifani (fib(30)) uch usulda bajardi (t37-piscina.txt, Node 24.21.0, Windows 11, i5-12500H):

text
16 vazifa, har biriga yangi Worker: 102 ms
16 vazifa, Piscina 4 thread (sovuq): 67 ms
16 vazifa, Piscina (issiq): 39 ms | completed: 32, utilization: 0.00
16 vazifa (fib 30): worker pool samarasi
  • Har biriga yangi Worker102 ms
  • Piscina, sovuq4 thread yaratiladi67 ms
  • Piscina, issiqthread'lar tayyor39 ms

Manba: IlmHamroh kanon o'lchovi, 2026-10-23: Node 24.21.0, piscina 5.3.2, Windows 11, i5-12500H (t37-piscina.txt)

E'tibor bering: "har biriga yangi Worker" 16 ta worker ochdi va 16 yadroda parallel ishladi. Pool esa bor-yo'g'i 4 thread bilan undan 2.6 baravar tez chiqdi (102 → 39 ms). Sababi — ishga tushish narxi: 16 × ~20 ms ishga tushishni pool bir marta to'laydi. "Sovuq" o'lchovda (67 ms) shu birinchi to'lov ko'rinadi.

utilization: 0.00 — Piscina'ning "thread'lar qancha band bo'lgan" ko'rsatkichi. Kanon uni o'lchov oxirida o'qidi, pool o'sha paytda bo'sh edi. Bu raqamga tayanmang.

4.2 Pool hajmi qancha bo'lsin?

Pool hajmini o'zimiz o'zgartirib ko'rdik: 32 vazifa (fib(30)), har hajm uchun isitishdan keyin 5 marta, mediana. Biz ishga tushirdik (Node 24.21.0, piscina 5.3.2, i5-12500H — 16 mantiqiy yadro, ikki marta). Chap ustun — birinchi seans, o'ng ustun — ikkinchisi:

text
1 thread: 520 ms        1 thread: 593 ms
2 thread: 168 ms        2 thread: 169 ms
4 thread: 83 ms         4 thread: 84 ms
8 thread: 61 ms         8 thread: 59 ms
16 thread: 41 ms        16 thread: 41 ms
32 vazifa (fib 30): pool hajmi va vaqt
  • 1 threadg'alati sekin — matnga qarang520 ms
  • 2 thread168 ms
  • 4 thread83 ms
  • 8 thread61 ms
  • 16 thread41 ms

Manba: IlmHamroh o'lchovi, 2026-10-23: Node 24.21.0, piscina 5.3.2, Windows 11, i5-12500H, 5 marta mediana

2 dan 4 ga — deyarli 2 baravar tezlashdi. 4 dan 8 ga — atigi 1.4 baravar, 8 dan 16 ga — 1.5. Nega chiziqli emas? i5-12500H da 16 mantiqiy yadro bor, lekin ular teng emas: 4 ta kuchli (P) yadro ikkitadan thread beradi, 8 tasi esa tejamkor (E) yadrolar. Bundan tashqari asosiy thread va operatsion tizim ham yadro oladi.

1 thread natijasi kutilganidan sekin (2 thread'dan 3 baravar). Ehtimol, OS yagona thread'ni tejamkor yadroga qo'ygan. Biz buni tekshirmadik — halol aytamiz: sabab aniq emas, faqat raqam haqiqiy.

Amaliy qoida: CPU-og'ir vazifalar uchun pool hajmi ≈ yadrolar soni, ko'pi bilan. Undan ko'p thread tezlashtirmaydi, faqat xotira oladi. Serverda esa asosiy thread va boshqa jarayonlar uchun bir-ikki yadro qoldiring. Eng to'g'risi — o'z serveringizda o'lchang.

Tekshirib ko'ring: 4 yadroli serverda pool'ga maxThreads: 32 qo'ysangiz, CPU-og'ir ish tezlashadimi?

Javob

Yo'q. Bir vaqtda faqat 4 ta thread haqiqatan ishlaydi, qolgan 28 tasi navbatda turadi va OS ularni almashtirib turadi. Almashtirishning o'zi vaqt oladi, har worker esa xotira oladi. Natija — o'sha tezlik yoki sekinroq, xotira esa ko'p. Ish kutishli (tarmoq, disk) bo'lsa, umuman worker kerak emas (oldingi dars).

5. Navbat va bekor qilish

5.1 maxQueue: navbatni cheklash

Vazifalar worker'lardan tez kelsa, navbat o'sadi. Cheklanmagan navbat — xotira sizishi va "hamma kutyapti" holati. maxQueue navbatni cheklaydi; to'lsa, yangi vazifa darhol rad etiladi:

js
const pool = new Piscina({
  filename: new URL("./ish.mjs", import.meta.url).href,
  maxThreads: 2,
  maxQueue: 2,
});
const vazifalar = [];
for (let i = 0; i < 6; i++) {
  const v = pool.run({ n: 27 });
  vazifalar.push(v.then(() => "ok", (x) => x.message));
}
console.log("navbatda:", pool.queueSize);
console.log(await Promise.all(vazifalar));
await pool.destroy();

Biz ishga tushirdik. Massivning oxirgi ikki elementiga qarang:

text
navbatda: 2
[
  'ok',
  'ok',
  'ok',
  'ok',
  'Task queue is at limit',
  'Task queue is at limit'
]

2 tasi worker'da, 2 tasi navbatda, oxirgi 2 tasi Task queue is at limit — "vazifalar navbati chegarada" xatosi bilan rad etildi. Bu yomon emas, aksincha — halol. Server "hozir bandman, keyinroq urinib ko'ring" deb javob beradi (HTTP'da bu 503 holati, 25-qismda o'rganamiz). Abadiy kutish va xotira tugashidan ko'ra yaxshiroq.

5.2 AbortSignal bilan bekor qilish

run ning ikkinchi argumentida signal beriladi (AbortController):

js
const pool = new Piscina({
  filename: new URL("./ish.mjs", import.meta.url).href,
  maxThreads: 1,
});
const ac = new AbortController();
const [birinchi, ikkinchi] = await Promise.allSettled([
  pool.run({ n: 30 }),
  pool.run({ n: 30 }, { signal: ac.signal }),
  ac.abort(),
]);
console.log(birinchi.status, birinchi.value);
console.log(ikkinchi.status, ikkinchi.reason.message);

Biz ishga tushirdik:

text
fulfilled 832040
rejected The task has been aborted

Ikkinchi vazifa navbatda turgan edi — u navbatdan olib tashlandi va rad etildi. Ishlayotgan vazifani ham bekor qilish mumkin: Piscina uning worker'ini to'xtatadi va o'rniga yangisini ochadi. Biz fib(42) ni (bir necha soniyalik ish) 50 ms dan keyin bekor qildik — Promise bir soniyadan oldin The task has been aborted bilan rad etildi.

Nega Promise.allSettled? Birinchi urinishimizda ikkinchi ni keyinroq await qilgan edik — va dastur yiqildi. Buni «Ko'p uchraydigan xatolar» bo'limida ko'ramiz.

6. tinypool va boshqa variantlar

tinypool — Piscina'dan ajralib chiqqan (fork) yengil pool. Uni Vitest jamoasi yozgan va Vitest testlarni shu bilan parallel yurgizadi (22-qismdagi Vitest'ni eslang). API deyarli bir xil:

js
import Tinypool from "tinypool";

const pool = new Tinypool({
  filename: new URL("./ish.mjs", import.meta.url).href,
  maxThreads: 4,
});
console.log(await pool.run({ n: 25 }));
console.log(await pool.run(7, { name: "kvadrat" }));
await pool.destroy();

Biz ishga tushirdik (tinypool 2.2.0): 75025 va 49 — Piscina bilan bir xil. Ikkalasini yonma-yon o'rnatganimizda node_modules hajmi:

Paket Versiya (2026-10) Hajm Native modul
piscina 5.3.2 717 KB (+ 413 KB @napi-rs) bor (thread ustuvorligi)
tinypool 2.2.0 81 KB yo'q, bog'liqlik 0

Qanday tanlash kerak?

  • Piscina — ko'proq imkoniyat (ustuvorlik, histogramma, kengaytiriladigan navbat), Node.js jamoasi a'zolari qo'llab-quvvatlaydi, ommabop. Server ilovasi uchun birinchi tanlov.
  • tinypool — minimal, bog'liqliksiz. Kichik vosita yoki kutubxona ichida, native modul istalmasa.
  • O'zingiz yozish — faqat o'rganish uchun (yuqoridagi mini-pool).

7. valyuta ga nega kirmadi

Kanon Piscina'ni alohida papkada o'lchadi va loyihaga qo'shmadi. Sabablar:

  1. valyuta da CPU-og'ir vazifa yo'q (oldingi darsdagi xulosa) — pool'ga beradigan ish yo'q.
  2. Kanon qoidasi — prod bog'liqlik 0. Piscina 3 paket va native modul olib keladi. CLI'ni o'rnatish vaqti va xavfsizlik sirti (npm xavfsizligi) o'sadi.
  3. Pool'ni ochish ham vaqt oladi (sovuq 67 ms va issiq 39 ms orasidagi farq). Har ishga tushishda bir marta ishlaydigan CLI uchun bu sof yo'qotish.

Pool uzoq yashaydigan jarayonda o'zini oqlaydi: server, bot, navbat ishlovchisi (queue worker) — u yerda worker'lar bir marta ochilib, soatlab ishlaydi.

8. Ko'p uchraydigan xatolar

8.1 Bekor qilingan vazifani kech await qilish

Biz birinchi urinishda shunday yozdik:

js
// ❌ ac.abort() ikkinchi Promise'ni darhol rad etadi,
// uni esa hali hech kim tinglamayapti
const birinchi = pool.run({ n: 30 });
const ikkinchi = pool.run({ n: 30 }, { signal: ac.signal });
ac.abort();
console.log(await birinchi);
try { await ikkinchi; } catch (x) { /* ... */ }

Natija — dastur yiqildi:

text
AbortError: The task has been aborted
    at …\node_modules\piscina\dist\index.js:426:24

ac.abort() navbatdagi vazifani shu zahotiyoq rad etadi. O'sha paytda ikkinchi ga hech kim catch ulamagan — biz birinchi ni kutyapmiz. Node buni ushlanmagan rad etilish deb hisoblaydi va jarayonni to'xtatadi. Har Promise'ga darhol ishlov bering: Promise.allSettled, yoki const ikkinchi = pool.run(...).catch(...).

8.2 pool.destroy() ni unutish

Pool worker'lari jarayonni tirik ushlab turadi — skript oxiriga yetsa ham tugamaydi. Skriptda — oxirida await pool.destroy(). Serverda — to'xtash signalida (process obyekti).

8.3 Nisbiy yo'l

filename: "./ish.mjs" — joriy papkaga (process.cwd()) nisbatan izlanadi, faylingizga nisbatan emas. Boshqa papkadan ishga tushirsangiz, worker topilmaydi. new URL("./ish.mjs", import.meta.url).href.

8.4 Pool'ga kutishli ishni berish

fetch yoki baza so'rovini pool'ga berish — thread'ni bekorga band qilish. Worker ichida ham u kutadi, faqat endi xotira ko'proq. Pool — faqat CPU-og'ir ish uchun.

8.5 Cheklanmagan navbat

Sukut bo'yicha Piscina navbati amalda cheksiz. Serverga yuk tushsa, navbat o'sib, xotira tugaydi. maxQueue qo'ying va to'lganda tushunarli javob bering.

9. Mashqlar

1-mashq (oson): Hisob-kitob

Bitta vazifa 10 ms hisob oladi, worker ishga tushishi 20 ms. 100 ta vazifa, 4 yadro. Taxminan qancha vaqt ketadi: (a) har vazifaga yangi worker, lekin bir vaqtda ko'pi bilan 4 ta; (b) 4 thread'li issiq pool?

Yechim

(a) Har vazifa 20 + 10 = 30 ms, to'rttadan parallel: 100 / 4 × 30 = 750 ms. (b) Worker'lar tayyor, faqat hisob: 100 / 4 × 10 = 250 ms. Pool 3 baravar tez — ishga tushish narxi vazifa vaqtidan katta bo'lgani uchun. Vazifa 1 soniyalik bo'lsa, farq atigi 2 % bo'lardi: pool kichik va ko'p vazifalarda o'zini ko'proq oqlaydi. Bu nazariy hisob; aniq raqam faqat o'lchovdan.

2-mashq (o'rta): Mini-pool'ga xato

«Pool ichida nima bor» bo'limidagi MiniPool worker ichidagi xatoni bilmaydi. Uni shunday o'zgartiring: worker { xato: xabar } yuborsa, vazifa shu xabar bilan rad etilsin, worker esa pool'da qolsin. Worker ichida n < 0 bo'lsa xato yuborilsin. Sinov: run(10), run(-1), run(12). Eslatma: try/catch worker ichida — yiqilmaslik uchun.

Yechim
js
import {
  Worker, isMainThread, parentPort,
} from "node:worker_threads";

const fib = (n) => (n < 2 ? n : fib(n - 1) + fib(n - 2));

class MiniPool {
  #bosh = [];
  #navbat = [];
  #hammasi = [];
  constructor(hajm) {
    for (let i = 0; i < hajm; i++) {
      const w = new Worker(new URL(import.meta.url));
      this.#hammasi.push(w);
      this.#bosh.push(w);
    }
  }
  run(n) {
    return new Promise((ok, yoq) => {
      this.#navbat.push({ n, ok, yoq });
      this.#tarqat();
    });
  }
  #tarqat() {
    while (this.#bosh.length && this.#navbat.length) {
      const w = this.#bosh.pop();
      const { n, ok, yoq } = this.#navbat.shift();
      w.once("message", (javob) => {
        if ("xato" in javob) yoq(new Error(javob.xato));
        else ok(javob.natija);
        this.#bosh.push(w);
        this.#tarqat();
      });
      w.postMessage(n);
    }
  }
  yop() {
    return Promise.all(this.#hammasi.map((w) => w.terminate()));
  }
}

if (isMainThread) {
  const pool = new MiniPool(1);
  const natijalar = await Promise.allSettled(
    [10, -1, 12].map((n) => pool.run(n)),
  );
  for (const r of natijalar) {
    const ok = r.status === "fulfilled";
    console.log(ok ? r.value : r.reason.message);
  }
  await pool.yop();
} else {
  parentPort.on("message", (n) => {
    try {
      if (n < 0) throw new Error("n manfiy bo'lmasin");
      parentPort.postMessage({ natija: fib(n) });
    } catch (xato) {
      parentPort.postMessage({ xato: xato.message });
    }
  });
}

Konsolda:

text
55
n manfiy bo'lmasin
144

Pool'da bitta worker bor edi, xato bo'lsa ham u keyingi vazifani (12) bajardi — worker o'lmadi. Bu Piscina'ning "xato worker'ni o'ldirmaydi" xulqining sodda nusxasi. Haqiqiy pool yana bitta holatni ham ushlaydi: worker baribir yiqilsa (exit), o'rniga yangisini ochadi.

3-mashq (qiyin): Piscina bilan hash xizmati

O'z kompyuteringizda alohida papkada npm i --save-exact piscina@5.3.2 qiling. Worker fayli hash.mjs { matn, marta } olib, matnni marta marta ketma-ket SHA-256 dan o'tkazsin (createHash — crypto 1) va hex natija qaytarsin. Asosiy fayl 8 ta vazifani maxThreads: 4, maxQueue: 2 bilan yuborsin va har biri "ok" yoki xato xabarini chiqarsin. Natijada nechta "ok" bo'lishini oldindan ayting.

Yechim
js
// hash.mjs
import { createHash } from "node:crypto";

export default function ({ matn, marta }) {
  let x = matn;
  for (let i = 0; i < marta; i++) {
    x = createHash("sha256").update(x).digest("hex");
  }
  return x;
}
js
// asosiy.mjs
import { Piscina } from "piscina";

const pool = new Piscina({
  filename: new URL("./hash.mjs", import.meta.url).href,
  maxThreads: 4,
  maxQueue: 2,
});
const ishlar = Array.from({ length: 8 }, (_, i) =>
  pool.run({ matn: `buyurtma-${i}`, marta: 200_000 })
    .then(() => "ok", (x) => x.message));
console.log(await Promise.all(ishlar));
await pool.destroy();

Kutiladigan natija: 4 tasi worker'larga, 2 tasi navbatga — 6 ta "ok", oxirgi 2 tasi Task queue is at limit. Biz ishga tushirdik (Node 24.21.0, piscina 5.3.2) — aynan shunday chiqdi:

text
[
  'ok',
  'ok',
  'ok',
  'ok',
  'ok',
  'ok',
  'Task queue is at limit',
  'Task queue is at limit'
]

Sukut bo'yicha minThreads 16 yadroli kompyuterda 8 bo'lardi, lekin maxThreads: 4 bir vaqtdagi ishni 4 bilan cheklaydi.

4-mashq: CLI qadami — tajriba, loyihaga kirmaydi

valyuta ga kod qo'shilmaydi. «valyuta ga nega kirmadi» bo'limidagi uch sababni o'z loyihangiz raqamlari bilan tekshiring: npm ls --omit=dev (prod bog'liqliklar — 0 bo'lishi kerak) va npm pack --dry-run dagi hajm. Keyin bir gapda yozing: qaysi holatda valyuta ga pool kerak bo'lib qolishi mumkin edi?

Yechim

Kanon holati: prod bog'liqlik 0, paket 26.2 kB (holat 35). Pool kerak bo'ladigan holatga misol: valyuta 10 yillik barcha valyutalar tarixidan murakkab statistika hisoblasa (masalan, har kun uchun korrelyatsiya matritsasi). Bu ish soniyalab davom etsa, uni bo'laklarga bo'lib, pool'da parallel hisoblash mumkin bo'lardi. Hozirgi tahlil esa 1 mln qatorni 0.8 s da va asosan fayl o'qishda bajaradi.

10. Real ishda

  • Rasm va fayl xizmatlari: thumbnail, PDF yaratish, Excel eksport — og'ir ishni pool'ga berib, server javob beruvchan qoladi.
  • Test va build vositalari: Vitest (tinypool), Jest (o'z pool'i), Webpack/Babel'ning parallel rejimlari — hammasi worker pool.
  • Navbat ishlovchilari: BullMQ kabi navbat tizimlari (keyingi qismlarda) vazifalarni Redis'dan olib, ishlovchilarga beradi — g'oya pool bilan bir xil, faqat jarayonlar va mashinalar orasida.
  • Intervyu savollari: "Worker pool nima va nega kerak?", "Pool hajmini qanday tanlaysiz?", "Navbat to'lsa nima qilasiz?".

Xulosa

  • Har vazifaga yangi worker — ~20 ms ishga tushish; pool worker'larni qayta ishlatadi.
  • Kanonda 16 vazifa: yangi Worker'lar bilan 102 ms, Piscina issiq — 39 ms.
  • Piscina: eksport qilingan funksiya + pool.run(ma'lumot, { name, signal }); xato worker'ni o'ldirmaydi.
  • Pool hajmi ≈ yadrolar soni; undan ko'pi tezlashtirmaydi. maxQueue bilan navbatni cheklang.
  • Bekor qilinadigan vazifaning Promise'iga darhol ishlov bering; oxirida pool.destroy().

Keyingi dars: cluster va ko'p yadroli masshtablash — worker thread emas, butun Node jarayonlarini ko'paytirib, bir nechta yadroda server yurgizamiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Node.js worker pool: Piscina bilan worker'larni qayta ishlatish — IlmHamroh