Mundarija (25)
- Bu darsda
- 1. Nega bu kerak?
- 2. Node ichidagi uch "ishchi"
- 2.1 Kim nima qiladi
- 2.2 Nega tarmoq pool'ga tushmaydi
- 2.3 Nega fayl pool'ga tushadi
- 3. Pool hajmi: 4 ta ishchi
- 3.1 O'lchov: 8 ta ish, har xil pool
- 3.2 Jonli model: to'lqinlarni hisoblash
- 3.3 UV_THREADPOOL_SIZE ni o'zgartirish
- 4. Pool to'lib qolishi
- 4.1 Tajriba: kichik ish katta navbatda
- 4.2 Natijani o'qiymiz
- 4.3 Tekshiriladigan variant: faqat tartib
- 5. Pool'ni nima band qiladi — ro'yxat
- 6. Pool to'lganda nima qilish kerak
- 7. CLI qadami: holat yo'q
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): qayerda bajariladi?
- 2-mashq (o'rta): navbatni hisoblang
- 3-mashq (qiyin): o'z o'lchovingiz
- 10. Real ishda
- Xulosa
- Manbalar
libuv thread pool va OS asinxron I/O: Node ishni kimga topshiradi
Qisqacha: JavaScript kodingiz bitta thread'da ishlaydi, lekin Node'ning o'zi bitta thread emas. Tarmoq ishlarini (TCP, HTTP) operatsion tizim o'zi asinxron bajaradi — Linux'da epoll, macOS'da kqueue, Windows'da IOCP orqali. Fayl tizimi (
fs),dns.lookup, og'ircryptovazlibesa libuv'ning thread pool'ida bajariladi — sukut bo'yicha 4 ta ishchi thread. Pool to'lib qolsa, hatto kichikreadFilevalocalhostgafetchham navbatda kutadi (o'lchandi).
Bu darsda
- Node'da qaysi ish OS'ga, qaysi biri thread pool'ga, qaysi biri JS thread'ga tushishini ajrata olasiz.
UV_THREADPOOL_SIZEni qachon va qanday o'zgartirishni bilasiz — va nega uni ko'r-ko'rona oshirmaslik kerakligini.- Pool to'lib qolishini o'z ko'zingiz bilan ko'rasiz: 4 ta
pbkdf2bitta kichik fayl o'qishni 700 ms ushlab turadi. fetch("http://localhost")bilanfetch("http://127.0.0.1")nega boshqacha kutishini tushuntira olasiz.- epoll, kqueue va IOCP nima ekanini bir gap bilan ayta olasiz.
Oldin bilishingiz kerak: Node.js nima: runtime, V8 va libuv, Blocking va non-blocking I/O, Event loop fazalari, Microtask, nextTick va setImmediate.
1. Nega bu kerak?
Node.js nima darsida "Node — bir threadli" degan gapni eshitdingiz. Blocking va non-blocking I/O darsida esa readFile kutish vaqtida loop boshqa ishni qilishini ko'rdingiz. Shu yerda tabiiy savol tug'iladi: agar thread bitta bo'lsa, faylni kim o'qiyapti?
Javob: JS kodingiz bitta thread'da ishlaydi, lekin Node ichida yana bir nechta "yordamchi" bor. Ularni bilmasangiz, bir kun g'alati muammoga duch kelasiz. Masalan, server parollarni hash qila boshlaganda (crypto darsida ko'rasiz) birdan fayl o'qish ham, tashqi API'ga so'rov ham sekinlashadi. CPU bo'sh turibdi, loop bloklanmagan — lekin hamma narsa kutyapti.
O'xshatish: katta oshxona. Oshpaz (JS thread) bitta — u buyurtmalarni qabul qiladi va tarqatadi. Go'shtni qiyma qilish uchun 4 ta yordamchi (thread pool) bor. Non esa tashqi novvoyxonadan keladi (operatsion tizim) — oshpaz faqat "olib keling" deydi va kutmaydi. Agar to'rt yordamchining to'rtalasi ham katta buyurtma bilan band bo'lsa, kichik "piyoz to'g'rang" topshirig'i ham navbatda turadi.
2. Node ichidagi uch "ishchi"
2.1 Kim nima qiladi
libuv — Node ichidagi C kutubxona. U event loop'ni yuritadi va I/O ishlarini bajaradi (Node.js nima darsida tanishgansiz). Uning ishi uch joyga taqsimlanadi:
| Ish | Qayerda bajariladi | Misollar |
|---|---|---|
| Sizning JS kodingiz | asosiy thread (V8) | sikl, JSON.parse, callback'lar |
| Tarmoq I/O | operatsion tizim | TCP, HTTP, fetch, dns.resolve |
| Fayl, DNS lookup, og'ir hisob | libuv thread pool | fs, dns.lookup, crypto.pbkdf2, zlib.gzip |
Diagrammada yo'nalishga qarang: JS faqat topshiradi va callback'ni oladi, og'ir ish boshqa joyda bo'ladi.
flowchart LR
JS["JS kodingiz<br/>(asosiy thread)"] --> L["libuv<br/>event loop"]
L -->|"tarmoq"| OS["OS: epoll / kqueue / IOCP"]
L -->|"fs, dns.lookup,<br/>crypto, zlib"| P["thread pool<br/>4 ishchi"]
OS -->|"tayyor"| L
P -->|"tayyor"| L
L -->|"callback"| JS2.2 Nega tarmoq pool'ga tushmaydi
Tarmoq bilan ishlashda dastur aslida hech narsa qilmaydi — u faqat ma'lumot kelishini kutadi. Zamonaviy operatsion tizimlarda buning uchun maxsus mexanizm bor: "mana shu 10 000 ta ulanishni kuzatib tur, qaysi biriga ma'lumot kelsa, menga ayt". Bitta tizim chaqiruvi minglab ulanishni kuzatadi.
Har OS'da bu mexanizmning nomi boshqacha:
| OS | Mexanizm | Qisqacha |
|---|---|---|
| Linux | epoll | "qaysi ulanish tayyor?" deb so'raladi |
| macOS, FreeBSD | kqueue | xuddi shu g'oya, BSD oilasida |
| Windows | IOCP (I/O Completion Ports) | "ish tugaganda xabar ber" — tugash haqida bildirish |
libuv bularning ustidan bitta umumiy qatlam yasaydi, shuning uchun sizning fetch kodingiz uchala tizimda bir xil ishlaydi. Bu qatlam tufayli bitta Node jarayoni minglab ochiq ulanishni ushlab tura oladi — Blocking va non-blocking I/O darsidagi C10k muammosining yechimi shu.
2.3 Nega fayl pool'ga tushadi
Fayllar bilan bunday qulay mexanizm hamma OS'da bir xil emas. Linux'ning epoll'i oddiy fayllar uchun ishlamaydi: disk "doim tayyor" deb javob beradi, o'qish esa baribir thread'ni to'xtatadi. Shuning uchun libuv oddiy yo'lni tanlagan: fayl amalini alohida thread'da sinxron bajaradi, tugagach natijani loop'ga qaytaradi. Sizning JS thread'ingiz uchun bu asinxron ko'rinadi.
dns.lookup ham shu sababdan pool'da. U OS'ning getaddrinfo funksiyasini chaqiradi, bu funksiya esa bloklovchi. crypto.pbkdf2, scrypt, randomBytes (callback'li), zlib.gzip kabilar umuman I/O emas — ular CPU ishi. Node ularni asosiy thread'ni to'xtatmaslik uchun pool'ga chiqaradi.
Maslahat: Linux'da libuv ba'zi fayl amallari uchun yangi
io_uringmexanizmini ham sinab ko'rgan. libuv hujjatiga ko'ra hozirgi versiyalarda u sukut bo'yicha o'chirilgan. Biz Windows'da o'lchadik — Linux'da sinalmagan, raqamlar farq qilishi mumkin.
Tekshirib ko'ring: Quyidagilardan qaysi biri thread pool'ga tushadi: (a)
fetch("https://cbu.uz/...")dagi ma'lumot kutish, (b)readFile("kurslar.json"), (c)JSON.parse(matn)?
Javob
Faqat (b). (a) — tarmoq, uni OS kuzatadi (Windows'da IOCP). (c) — oddiy JS kodi, u asosiy thread'da bajariladi va pool'ga hech qachon chiqmaydi. Katta JSON.parse loop'ni bloklaydi — buni Event loop'ni bloklash darsida o'lchaymiz.
3. Pool hajmi: 4 ta ishchi
3.1 O'lchov: 8 ta ish, har xil pool
Kanon tajribasi: 8 ta pbkdf2 (parolni hash qilish, har biri 300 000 marta takrorlanadi) bir vaqtda boshlandi. Har safar UV_THREADPOOL_SIZE muhit o'zgaruvchisi bilan pool hajmi o'zgartirildi. Har hajm uch marta, alohida jarayonda o'lchandi:
UV_THREADPOOL_SIZE=1: 8 × pbkdf2(300k) — mediana 2155 ms
UV_THREADPOOL_SIZE=2: 8 × pbkdf2(300k) — mediana 965 ms
UV_THREADPOOL_SIZE=4: 8 × pbkdf2(300k) — mediana 560 ms
UV_THREADPOOL_SIZE=8: 8 × pbkdf2(300k) — mediana 381 msGrafikda ko'ring — har ikki baravar ishchi vaqtni deyarli ikki baravar kamaytiradi, lekin 8 da tejash kamayadi:
- 1 threadhammasi navbat bilan2 155 ms
- 2 thread965 ms
- 4 thread (sukut)ikki to'lqin560 ms
- 8 threadbitta to'lqin381 ms
Manba: O'lchandi: IlmHamroh kanoni, Node 24.21.0, Windows 11, i5-12500H (16 mantiqiy yadro), har biri 3 jarayon mediana, 2026-10-23
Sukut bo'yicha pool'da 4 ta thread. 8 ta ish ikki "to'lqin" bo'lib bajariladi: avval 4 tasi, keyin qolgan 4 tasi. Pool 8 ta bo'lganda hammasi bir to'lqinda tugaydi.
3.2 Jonli model: to'lqinlarni hisoblash
Pool qanday ishlashini oddiy model bilan ko'ramiz. Har ish eng birinchi bo'shagan ishchiga beriladi. Bitta pbkdf2 270 ms deylik:
function simulatePool(size, durations) {
const freeAt = Array(size).fill(0); // har ishchi qachon bo'shaydi
return durations.map((ms) => {
const worker = freeAt.indexOf(Math.min(...freeAt));
freeAt[worker] += ms;
return freeAt[worker]; // shu ish tugagan vaqt
});
}
const jobs = Array(8).fill(270);
for (const size of [1, 2, 4, 8]) {
const done = simulatePool(size, jobs);
console.log(`${size} thread: ${Math.max(...done)} ms`);
}Konsolda:
1 thread: 2160 ms
2 thread: 1080 ms
4 thread: 540 ms
8 thread: 270 msModel 1, 2 va 4 da o'lchovga juda yaqin. 8 da esa model 270 deydi, haqiqat 381. Nega? Protsessor yadrolari ham cheksiz emas: 8 ta og'ir hisob bir vaqtda ishlaganda protsessor chastotasini pasaytiradi, "giper-thread" yadrolar to'liq yadrodek tez emas. Thread qo'shish — yadro qo'shish emas.
3.3 UV_THREADPOOL_SIZE ni o'zgartirish
Pool hajmi jarayon boshlanishida o'rnatiladi:
UV_THREADPOOL_SIZE=8 node server.jsWindows PowerShell'da: $env:UV_THREADPOOL_SIZE=8; node server.js. Qoidalar (libuv va Node hujjatlari):
- Sukut — 4, eng ko'pi — 1024.
- Pool birinchi marta kerak bo'lganda yaratiladi. Shundan keyin o'zgartirish ta'sir qilmaydi.
- Kod ichida
process.env.UV_THREADPOOL_SIZE = "8"ni eng birinchi qatorga yozish ba'zan ishlaydi. Biz Windows'da sinadik: pool hajmi haqiqatan 1 ga tushdi (4 ta pbkdf2 — 1 131 ms, sukutda 301 ms). Lekin bu "pool hali yaratilmagan" degan taxminga tayanadi. Ishonchli yo'l — muhit o'zgaruvchisi.
Pool kattalashsa nima yo'qotasiz? Har thread xotira oladi va yadrolar uchun raqobat kuchayadi. 16 yadroli mashinada 128 ta pbkdf2 thread'i ishni tezlatmaydi, faqat almashinish ko'payadi. Odatiy maslahat: CPU ishi uchun yadrolar sonidan oshirmang; ko'p fayl kutadigan dastur uchun biroz kattaroq bo'lishi mumkin. Har holda — o'lchang.
Tekshirib ko'ring: Kod
server.listen()dan keyin, 10 daqiqa ishlagach,process.env.UV_THREADPOOL_SIZE = "16"qiladi. Pool kattalashadimi?
Javob
Yo'q. Pool o'sha paytgacha allaqachon yaratilgan (server ishga tushganda ham fs yoki dns.lookup ishlatilgan bo'lishi deyarli aniq). Hajm faqat jarayon boshida o'qiladi — qayta ishga tushirish kerak.
4. Pool to'lib qolishi
4.1 Tajriba: kichik ish katta navbatda
Endi eng qiziq qismi. 4 ta og'ir pbkdf2 (har biri 1 000 000 takror) pool'ning to'rtala ishchisini band qiladi. Shu paytda biz:
127.0.0.1gafetchyuboramiz;localhostgafetchyuboramiz;dns.lookup("localhost")qilamiz;- kichik faylni o'qiymiz (
readFile).
Biz ishga tushirdik (Node 24.21.0, Windows 11, i5-12500H). Vaqtlar har safar biroz farq qiladi, shuning uchun bu blok tekshiruvchida emas, haqiqiy chiqish pastda:
import { pbkdf2 } from "node:crypto";
import { readFile } from "node:fs/promises";
import { lookup } from "node:dns/promises";
import { createServer } from "node:http";
const server = createServer((req, res) => res.end("ok"));
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const port = server.address().port;
const start = performance.now();
const t = () => `${Math.round(performance.now() - start)} ms`;
for (let i = 1; i <= 4; i++) {
pbkdf2("parol", "tuz", 1_000_000, 64, "sha512", () =>
console.log(`pbkdf2 #${i} tugadi: ${t()}`));
}
fetch(`http://127.0.0.1:${port}/`).then(() =>
console.log(`fetch 127.0.0.1: ${t()}`));
fetch(`http://localhost:${port}/`).then(() =>
console.log(`fetch localhost: ${t()}`));
lookup("localhost").then(() => console.log(`dns.lookup: ${t()}`));
readFile(import.meta.filename).then(() =>
console.log(`readFile: ${t()}`));
setTimeout(() => server.close(), 3000);Sukut (4 thread):
fetch 127.0.0.1: 33 ms
pbkdf2 #4 tugadi: 775 ms
dns.lookup: 780 ms
pbkdf2 #1 tugadi: 782 ms
readFile: 784 ms
fetch localhost: 785 ms
pbkdf2 #2 tugadi: 820 ms
pbkdf2 #3 tugadi: 855 msUV_THREADPOOL_SIZE=8 bilan:
dns.lookup: 26 ms
fetch 127.0.0.1: 44 ms
readFile: 45 ms
fetch localhost: 48 ms
pbkdf2 #3 tugadi: 860 ms
pbkdf2 #2 tugadi: 876 ms
pbkdf2 #4 tugadi: 917 ms
pbkdf2 #1 tugadi: 957 ms4.2 Natijani o'qiymiz
Birinchi jadvalda uchta narsaga qarang:
fetch 127.0.0.1— 33 ms. IP manzil tayyor, DNS kerak emas. Ulanish va javob — tarmoq, ya'ni OS (IOCP). Pool band ekani unga ta'sir qilmadi.dns.lookupvareadFile— ~780 ms. Ikkalasi ham pool'da. Ular birinchipbkdf2tugashini — ishchi bo'shashini — kutdi. Bir necha baytli fayl 780 ms o'qildi!fetch localhost— 785 ms. Eng kutilmagani shu.localhost— nom, uni IP'ga aylantirish kerak.fetchbuning uchundns.lookupni ishlatadi — demak u ham pool navbatiga tushdi. Tarmoq so'rovi bo'lsa ham, DNS qismi tufayli kutdi.
8 thread bilan hammasi ~45 ms da tugadi: pool'da bo'sh ishchi qoldi. pbkdf2 lar esa biroz sekinlashdi (775–855 → 860–957 ms) — endi ular 4 ta "kichik" ish bilan yadro bo'lishdi.
Bu holatni pool to'lib qolishi (thread pool saturation) deyishadi. Belgisi: CPU bo'sh emas, lekin event loop ham bloklanmagan. Shunga qaramay fayl, DNS va nom bilan so'rovlar sekin.
4.3 Tekshiriladigan variant: faqat tartib
Vaqtni emas, faqat "kim kimni kutdi" ni chiqarsak, natija har safar bir xil. Bu blok tekshiruvchida haqiqiy Node 24 da ishga tushiriladi. Saytda «Ishga tushir» tugmasi yo'q — uni o'z terminalingizda node pool.mjs bilan yurgizing:
import { pbkdf2 } from "node:crypto";
import { readFile } from "node:fs/promises";
import { createServer } from "node:http";
const server = createServer((req, res) => res.end("ok"));
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const { port } = server.address();
let hashed = 0; // nechta pbkdf2 tugadi
for (let i = 0; i < 4; i++) {
pbkdf2("parol", "tuz", 1_000_000, 64, "sha512", () => {
hashed++;
if (hashed === 4) server.close();
});
}
await fetch(`http://127.0.0.1:${port}/`);
console.log(`fetch 127.0.0.1 — tugagan pbkdf2: ${hashed}`);
await readFile(import.meta.filename);
console.log(`readFile — tugagan pbkdf2: ${hashed > 0 ? "1+" : 0}`);Konsolda:
fetch 127.0.0.1 — tugagan pbkdf2: 0
readFile — tugagan pbkdf2: 1+fetch hech bir pbkdf2 ni kutmadi. readFile esa kamida bittasi tugamaguncha boshlana olmadi.
Diqqat:
readFilebitta amal emas: u faylni ochadi, hajmini so'raydi, o'qiydi va yopadi. Har biri pool'ga alohida navbatga turadi. Shuning uchun to'lgan pool'da katta fayl bo'lak-bo'lak o'qilganda kechikish bir necha marta qo'shiladi.
Tekshirib ko'ring: Server tashqi API'ga
fetch("https://cbu.uz/...")yuboradi va bir vaqtda foydalanuvchi parollariniscryptbilan hash qiladi. Nega API so'rovlari sekinlashishi mumkin?
Javob
cbu.uz — nom. fetch uni dns.lookup bilan IP'ga aylantiradi, dns.lookup esa thread pool'da. scrypt lar pool'ni band qilsa, DNS so'rovi navbatda turadi. Ulanishning o'zi va javob kutish esa OS'da — ular sekinlashmaydi.
5. Pool'ni nima band qiladi — ro'yxat
Node hujjatining "Don't Block the Event Loop" qo'llanmasi va libuv hujjati bo'yicha:
| Modul | Pool'da (callback/Promise variant) | Pool'da EMAS |
|---|---|---|
fs |
deyarli hamma amal | fs.watch (OS kuzatadi), *Sync (asosiy thread) |
dns |
lookup, lookupService |
resolve* — c-ares orqali, tarmoqda |
crypto |
pbkdf2, scrypt, randomBytes, randomFill, generateKeyPair |
createHash — sinxron, asosiy thread |
zlib |
gzip, deflate, brotliCompress (async) |
gzipSync — asosiy thread |
dns.resolve4 va dns.lookup farqiga e'tibor bering. lookup OS sozlamalarini (hosts fayli, localhost) hisobga oladi va brauzer kabi ishlaydi. resolve esa to'g'ridan DNS serverga so'rov yuboradi va pool'ni band qilmaydi. fetch va http.request sukut bo'yicha lookup ni ishlatadi. Ikkalasining farqini net, dns va tls darsida chuqur ko'rasiz.
Sinxron (Sync) variantlar pool'ga umuman tushmaydi — ular to'g'ridan asosiy thread'ni to'xtatadi. createHash ham sinxron: u kichik matnni mikrosekundlarda hash qiladi, shuning uchun pool'ga chiqarishning ma'nosi yo'q. Bizning valyuta CLI'da kesh kaliti aynan createHash bilan yasaladi (crypto 1) — pool'ga tegmaydi.
6. Pool to'lganda nima qilish kerak
Muammoni ko'rdik. Yechimlar arzonidan boshlab:
- O'lchang. Belgilar: fayl va DNS sekin, loop kechikishi past (Event loop'ni bloklash darsidagi
monitorEventLoopDelay). Bir xil ishniUV_THREADPOOL_SIZEbilan va usiz solishtiring. - Og'ir CPU ishini pool'dan ajrating. Parol hash qilish kabi ishni alohida worker_threads yoki alohida xizmatga chiqaring. Shunda fayl va DNS uchun pool bo'sh qoladi.
- Bir vaqtdagi og'ir ishlar sonini cheklang. 1 000 foydalanuvchi bir vaqtda kirsa, 1 000 ta
scryptni birdaniga pool'ga tashlamang — navbat qiling (keyingi darslarda "concurrency limit" naqshini ko'rasiz). - DNS'ni keshlang yoki IP bilan ulaning — ichki xizmatlar uchun.
- Oxirida — pool hajmini oshiring, o'lchov bilan. Yadrolar sonidan ancha oshirish odatda foyda bermaydi.
7. CLI qadami: holat yo'q
Bu darsda valyuta kodi o'zgarmaydi. Sabab o'lchovda: CLI bitta buyruqda bitta fetch yoki bitta readFile qiladi. Pool'ning 4 ishchisidan bittasi ham to'liq band bo'lmaydi. Kesh kaliti createHash bilan sinxron (pool'ga tegmaydi), tarmoq so'rovi esa OS'da.
Lekin valyuta da bitta foydali saboq bor. Uning manba moduli CBU'ga nom bilan (cbu.uz) murojaat qiladi — demak birinchi so'rovda dns.lookup pool'dan o'tadi. Agar kelajakda CLI'ni serverga aylantirsangiz va unda og'ir hisob bo'lsa, bu darsdagi "localhost ham kutdi" holati sizda ham takrorlanadi.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
Fayl o'qish yoki fetch birdan sekin, CPU band |
pool crypto/zlib bilan to'lgan |
og'ir ishni worker'ga, sonini cheklang |
UV_THREADPOOL_SIZE=64 qo'yildi, tezlashmadi |
ish CPU'ga bog'liq, yadrolar soni cheklangan | yadrolar sonidan oshirmang, o'lchang |
Kodda process.env.UV_THREADPOOL_SIZE o'zgartirildi, ta'sir yo'q |
pool allaqachon yaratilgan | jarayonni muhit o'zgaruvchisi bilan ishga tushiring |
"Node bir threadli — demak readFile loop'ni to'xtatadi" |
JS thread va libuv thread'lari aralashtirildi | readFile pool'da; readFileSync esa JS thread'da |
localhost sekin, 127.0.0.1 tez |
nom → dns.lookup → pool navbati |
ichki ulanishda IP; pool'ni bo'shating |
9. Mashqlar
Mashqlar kurs/mashqlar/24/05-thread-pool/ da.
1-mashq (oson): qayerda bajariladi?
Har amal uchun yozing: JS (asosiy thread), pool yoki OS.
fs.promises.readFile("a.txt")—crypto.createHash("sha256").update("x").digest("hex")—fetch("http://127.0.0.1:3000")dagi javob kutish —zlib.gzip(buf, cb)—dns.lookup("example.com", cb)—
Yechim
- pool — fayl amallari libuv pool'ida sinxron bajariladi.
- JS —
createHashsinxron, kichik matn uchun pool keraksiz. - OS — IP manzil bilan tarmoq ishi epoll/kqueue/IOCP orqali.
- pool — callback'li
zlibpool'da;gzipSyncesa JS thread'da. - pool —
getaddrinfobloklovchi, shuning uchun pool'da.dns.resolve4bo'lsa — pool emas.
2-mashq (o'rta): navbatni hisoblang
«Jonli model» blokidagi simulatePool dan foydalaning. Pool 4 ta. Avval 4 ta og'ir ish (har biri 700 ms), keyin 3 ta yengil ish (har biri 5 ms) keladi. Har yengil ish qachon tugashini chiqaring. Keyin pool 8 ta bo'lganda ham hisoblang.
Yechim
function simulatePool(size, durations) {
const freeAt = Array(size).fill(0);
return durations.map((ms) => {
const worker = freeAt.indexOf(Math.min(...freeAt));
freeAt[worker] += ms;
return freeAt[worker];
});
}
const jobs = [700, 700, 700, 700, 5, 5, 5];
for (const size of [4, 8]) {
const light = simulatePool(size, jobs).slice(4);
console.log(`${size} thread: yengillar ${light.join(", ")} ms`);
}Konsolda:
4 thread: yengillar 705, 705, 705 ms
8 thread: yengillar 5, 5, 5 ms4 thread'da yengil ishlar 700 ms gacha kutadi: to'rtala ishchi band. 700 ms da hammasi bo'shaydi va uchala yengil ish uch xil ishchiga tushib, 705 ms da tugaydi. 8 thread'da 4 ta ishchi bo'sh — yengillar darhol bajariladi. Bu «Pool to'lib qolishi» tajribasining kichik nusxasi: readFile ~780 ms kutdi.
3-mashq (qiyin): o'z o'lchovingiz
Node 24 da pool-olch.mjs yozing: 8 ta pbkdf2("p", "s", 300_000, 64, "sha512", cb) ni bir vaqtda boshlang va hammasi tugagan vaqtni millisekundda chiqaring. Uni UV_THREADPOOL_SIZE=1, 2, 4, 8 bilan to'rt marta ishga tushiring. Natijangizni «Pool hajmi: 4 ta ishchi» bo'limidagi grafik bilan solishtiring. Kompyuteringizda nechta yadro borligini os.availableParallelism() bilan chiqaring.
Yechim
import { pbkdf2 } from "node:crypto";
import { availableParallelism } from "node:os";
const size = process.env.UV_THREADPOOL_SIZE ?? "4 (sukut)";
const start = performance.now();
let left = 8;
for (let i = 0; i < 8; i++) {
pbkdf2("p", "s", 300_000, 64, "sha512", () => {
left--;
if (left > 0) return;
const ms = Math.round(performance.now() - start);
console.log(`pool ${size}: ${ms} ms`);
console.log(`yadrolar: ${availableParallelism()}`);
});
}Biz ishga tushirdik (UV_THREADPOOL_SIZE=4, Node 24.21.0, i5-12500H):
pool 4: 756 ms
yadrolar: 16O'lchov paytida kompyuterda boshqa og'ir jarayonlar ham ishlagan — kanonning toza o'lchovi 560 ms edi. Sizda raqamlar boshqacha bo'ladi — muhimi nisbat: 1 → 2 → 4 da vaqt deyarli ikki baravardan kamayadi, 8 da tejash kichrayadi. Yadrolaringiz 4 ta bo'lsa, 8 thread deyarli foyda bermasligi kerak. PowerShell'da: $env:UV_THREADPOOL_SIZE=1; node pool-olch.mjs.
10. Real ishda
- Serverlarda parol hash qilish — crypto 2 darsida
scryptvaargon2ni ko'rasiz. Ular ataylab sekin. Login sahifasiga yuklama tushganda pool to'ladi va fayl o'qish, DNS ham sekinlashadi — production'dagi "sirli sekinlik" sabablaridan biri. - Rasm va siqish —
sharpkabi kutubxonalar ham libuv pool'ida ishlaydi; ko'p rasm bir vaqtda qayta ishlansa, xuddi shu muammo. - Intervyu savoli: "Node bir threadli bo'lsa, fayl qanday asinxron o'qiladi?" Javob: JS bitta thread'da, libuv fayl amallarini 4 thread'li pool'da bajaradi, tarmoqni esa OS (epoll/kqueue/IOCP) kuzatadi.
- Monitoring — pool'ning o'z o'lchagichi yo'q. Amalda fayl va DNS kechikishi, loop kechikishi va CPU birga kuzatiladi (Diagnostika vositalari).
Xulosa
- JS kodingiz — bitta thread; libuv esa ishni OS'ga va thread pool'ga taqsimlaydi.
- Tarmoq — OS mexanizmlari: Linux'da epoll, macOS'da kqueue, Windows'da IOCP.
- Pool'da:
fs,dns.lookup, og'ircrypto(pbkdf2,scrypt), asynczlib. Sukut — 4 thread,UV_THREADPOOL_SIZEbilan 1024 gacha. - Pool to'lsa, kichik
readFileva nom bilanfetchham kutadi (o'lchandi: ~780 ms). - Yechim tartibi: o'lchash → og'ir ishni ajratish → sonini cheklash → oxirida pool hajmi.
Keyingi dars: Event loop'ni bloklash: aniqlash va oldini olish — bu safar muammo pool'da emas, asosiy thread'ning o'zida: og'ir sikl, katta JSON.parse va ReDoS'ni o'lchaymiz.
Manbalar
- Node.js qo'llanmasi — Don't Block the Event Loop (or the Worker Pool): https://nodejs.org/en/learn/asynchronous-work/dont-block-the-event-loop
- libuv hujjati — Thread pool work scheduling (
UV_THREADPOOL_SIZE, 1024 chegarasi): https://docs.libuv.org/en/v1.x/threadpool.html - libuv hujjati — Design overview (epoll, kqueue, IOCP; fayl I/O thread pool'da): https://docs.libuv.org/en/v1.x/design.html
- Node.js hujjati —
dns.lookupvadns.resolvefarqi (Implementation considerations): https://nodejs.org/docs/latest-v24.x/api/dns.html#implementation-considerations - Kanon:
valyutatajribasi — 8 × pbkdf2, pool hajmi 1/2/4/8 (Node 24.21.0, Windows 11, i5-12500H, 2026-10-23); pool to'lishi valocalhost/127.0.0.1tajribasi — yozuvchi o'lchovi, xuddi shu mashina.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!