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

Blocking va non-blocking I/O: Node bitta thread bilan qanday ulguradi

Qisqacha: Blocking (bloklovchi) chaqiruv natija kelguncha JavaScript thread'ini to'xtatib turadi — bu vaqtda boshqa hech narsa bajarilmaydi. Non-blocking (bloklamaydigan) chaqiruv ishni Node'ga topshirib darhol qaytadi, natija keyin callback yoki Promise orqali keladi. Asinxron o'qish tezroq emas — 200 MB faylda u hatto biroz sekinroq chiqdi. Uning foydasi boshqa: kutish vaqtida thread bo'sh va minglab boshqa so'rovga xizmat qila oladi.

Bu darsda

  • Blocking va non-blocking chaqiruv farqini misol bilan tushuntira olasiz.
  • Node'dagi bir ishning uch shaklini (readFileSync, callback, fs/promises) taniysiz va qaysi birini tanlashni bilasiz.
  • readFileSync serverda nega xavfli, CLI skriptda nega joiz ekanini asoslaysiz.
  • O'lchov natijasini o'qiysiz: kutish paytida taymer 0 marta va 149 marta ishlagan holatlar.
  • C10k muammosi nima ekanini va Node unga qanday javob berishini aytasiz.

Oldin bilishingiz kerak: Node.js nima: runtime, V8 va libuv, Sinxron va asinxron kod, Callbacklar va callback hell, Ketma-ket va parallel bajarish.

1. Nega bu kerak?

Node.js nima: runtime, V8 va libuv darsida bitta fikr bilan tugatdik: JavaScript bitta thread'da bajariladi, kutish esa fonda bo'ladi. Endi savol: bu nima beradi va qayerda buzilishi mumkin?

«Bahor» oshxonasining kassasini tasavvur qiling. Kassada bitta odam turibdi — Jasur aka.

  • Birinchi usul. Ali osh buyurtma qildi. Jasur aka oshxonaga boradi va osh tayyor bo'lguncha o'sha yerda kutadi. Navbatdagi Malika va boshqalar shu 10 daqiqa turib qoladi.
  • Ikkinchi usul. Jasur aka Ali'ga raqamcha beradi, buyurtmani oshxonaga yuboradi va darhol Malika'ni qabul qiladi. Osh tayyor bo'lganda oshxona qo'ng'iroq chaladi, Jasur aka raqamni chaqiradi.

Ikkala usulda osh bir xil tezlikda pishadi. Lekin ikkinchisida bitta kassir yuzlab odamga xizmat qiladi. Birinchi usul — blocking, ikkinchisi — non-blocking. Node ikkinchi usul uchun yaratilgan.

2. I/O va kutish

2.1 Dastur vaqti qayerga ketadi

Node.js nima: runtime, V8 va libuv darsida I/O (input/output) atamasini ko'rdik — dasturning tashqi dunyo bilan almashuvi. Diskdan o'qish, ma'lumotlar bazasiga so'rov, boshqa serverga fetch, terminaldan kiritish — hammasi I/O.

I/O'ning umumiy xususiyati: ish dasturning o'zida emas, tashqarida bajariladi. Disk o'qiydi, tarmoq paketlarni yetkazadi, baza qidiradi. Dastur faqat kutadi. Odatiy backend so'rovida vaqtning katta qismi aynan shu kutishga ketadi: JavaScript qatorlari millisekundning bir qismida bajariladi, baza esa javobni bir necha millisekundda qaytaradi.

Demak, savol "kodni qanday tezlashtirish" emas. Savol: kutish vaqtida thread nima qiladi?

2.2 Ikki javob

Blocking Non-blocking
Kutish vaqtida thread to'xtab turadi boshqa ishni qiladi
Natija qanday keladi funksiya qaytaradi callback yoki Promise
Node'dagi nomi ...Sync bilan tugaydi Sync siz
Kassir o'xshatishi oshxonada turib kutadi raqamcha beradi

Sxemada non-blocking o'qish qanday ketishini ko'ring. Yuqoridan pastga vaqt o'tadi:

sequenceDiagram
  participant JS as JS thread
  participant U as libuv
  participant D as Disk
  JS->>U: readFile("menyu.txt")
  U->>D: o'qish so'rovi
  Note over JS: boshqa kod bajariladi
  D-->>U: baytlar tayyor
  U-->>JS: callback navbatga
  Note over JS: callback ishlaydi

Nimaga qarang: JS thread chiziqi o'rtada uzilmaydi — u disk javobini kutmasdan boshqa ishga o'tadi. Blocking variantda esa JS thread "o'qish so'rovi"dan "baytlar tayyor"gacha hech narsa qilmasdi.

Tekshirib ko'ring: Quyidagilardan qaysi biri I/O: (a) 1 dan 1 000 000 gacha sonlarni qo'shish, (b) CBU saytidan valyuta kursini olish, (c) kurslar.json faylini o'qish?

Javob

(b) va (c) — I/O: biri tarmoq, biri disk. Ikkalasida dastur tashqi qurilma javobini kutadi. (a) — I/O emas, bu CPU ishi (hisoblash): uni JavaScript thread'ining o'zi bajaradi va hech narsani kutmaydi. Bu farq muhim: non-blocking I/O kutishni bo'shatadi, lekin og'ir hisobni bo'shata olmaydi.

3. Blocking: thread band bo'lganda

3.1 Bloklangan taymer

Blocking'ni saytda ham ko'rish mumkin — buning uchun fayl shart emas. Quyidagi blokda bo'sh sikl 300 ms davomida thread'ni band qiladi. U og'ir hisobni yoki readFileSync ni "o'ynaydi". Sinxron va asinxron kod darsida bo'sh sikl bilan kutish xato ekanini aytgan edik — bu yerda ataylab, bloklashni ko'rsatish uchun ishlatamiz. «Ishga tushir» ni bosing:

js
const boshi = Date.now();

setTimeout(() => {
  const kechikish = Date.now() - boshi;
  console.log("Taymer: 300 ms dan kech?", kechikish >= 300);
}, 10);

// thread'ni 300 ms band qilamiz
while (Date.now() - boshi < 300) {}
console.log("Og'ir ish tugadi");

Konsolda:

text
Og'ir ish tugadi
Taymer: 300 ms dan kech? true

Taymer 10 ms ga qo'yilgan edi. Lekin u 300 ms dan keyin ishladi. Sababi — Event loop qoidasi: callback faqat call stack bo'shaganda bajariladi. Sikl ishlayotganda stek bo'sh emas. Taymer vaqti keldi, navbatga tushdi va kutib turdi.

Node serverda xuddi shu narsa so'rovlar bilan bo'ladi. Bitta so'rov thread'ni 300 ms band qilsa, shu vaqtda kelgan har bir boshqa so'rov ham 300 ms kutadi.

3.2 ...Sync funksiyalar ham bloklaydi

Node'da fayl bilan ishlaydigan ko'p funksiyaning Sync varianti bor: readFileSync, writeFileSync, existsSync, mkdirSync. Ular natija tayyor bo'lguncha qaytmaydi — ya'ni yuqoridagi sikl kabi thread'ni ushlab turadi. Farqi faqat bitta: thread hisoblamaydi, diskni kutadi. Lekin JavaScript uchun natija bir xil — boshqa hech narsa bajarilmaydi.

Buni haqiqiy faylda tekshiramiz. 50 MB fayl yaratamiz va uni ikki usulda o'qiymiz. Har bosqich boshida 0 ms li taymer qo'yamiz va u qachon ishlashini kuzatamiz. Bu blok faqat Node'da ishlaydi — .mjs faylga saqlab, terminalda sinang:

js
import { writeFileSync, readFileSync } from "node:fs";
import { readFile } from "node:fs/promises";

writeFileSync("katta.txt", "x".repeat(50_000_000)); // 50 MB
const kut = () => new Promise((r) => setTimeout(r, 5));

setTimeout(() => console.log("  taymer ishladi"), 0);
readFileSync("katta.txt");
console.log("Sync: fayl keldi");
await kut();

setTimeout(() => console.log("  taymer ishladi"), 0);
const vada = readFile("katta.txt");
console.log("Async: o'qish boshlandi");
await vada;
console.log("Async: fayl keldi");

Konsolda:

text
Sync: fayl keldi
  taymer ishladi
Async: o'qish boshlandi
  taymer ishladi
Async: fayl keldi

Ikki bosqichni solishtiring:

  • Sync bosqichi: taymer fayl kelgandan keyin ishladi. readFileSync o'qish tugaguncha thread'ni bermadi.
  • Async bosqichi: taymer fayl kelishidan oldin ishladi. readFile o'qishni libuv'ga topshirdi, thread bo'shadi, event loop taymerni bajardi.

await kut() ikkala bosqichni ajratadi: birinchi taymer ikkinchi bosqichga aralashmasin. Blokni biz Node 24.21.0 da 8 marta ishga tushirdik — tartib har safar bir xil chiqdi.

Tekshirib ko'ring: Yuqoridagi kodda readFileSync o'rniga existsSync("katta.txt") bo'lsa, u ham thread'ni bloklaydimi?

Javob

Ha, nomida Sync bor — natija qaytguncha thread kutadi. Lekin existsSync faqat faylning bor-yo'qligini so'raydi, butun faylni o'qimaydi, shuning uchun juda qisqa bloklaydi. Bloklash bor-yo'qligi Sync so'zidan bilinadi, uning qanchalik zararli ekanini esa davomiyligi belgilaydi.

4. Non-blocking: bir ishning uch shakli

4.1 Uchta API

Node'da fayl o'qishning uchta shakli bor. Uchalasi bir xil ishni qiladi — faylni o'qiydi. Farq natija qanday kelishida:

Shakl Qayerdan Natija Bloklaydimi
readFileSync(f) node:fs qaytaradi ha
readFile(f, cb) node:fs callback'ga yo'q
readFile(f) node:fs/promises Promise yo'q

Callback shaklini Callbacklar va callback hell darsida error-first kelishuvi bilan ko'rgan edingiz: birinchi parametr — xato, ikkinchisi — natija. Promise shakli esa async/await bilan ishlaydi.

Zamonaviy Node kodida asosiy tanlov — node:fs/promises. Callback shakli eski kodda va ba'zi kutubxonalar ichida ko'p uchraydi. Ikkisi ham ichkarida bir xil libuv mexanizmiga tushadi.

4.2 Promise shakli amalda

js
import { writeFile, readFile } from "node:fs/promises";

await writeFile("kurs.txt", "USD 11903.15");

try {
  const matn = await readFile("kurs.txt", "utf8");
  console.log("O'qildi:", matn);
  await readFile("yoq.txt", "utf8");
} catch (xato) {
  console.log("Xato kodi:", xato.code);
}

Konsolda:

text
O'qildi: USD 11903.15
Xato kodi: ENOENT

ENOENT — "Error NO ENTry", ya'ni "bunday yozuv (fayl) yo'q". Node tizim xatolarini shunday qisqa kodlar bilan beradi: ENOENT, EACCES (ruxsat yo'q), EEXIST (allaqachon bor). Xato matni emas, aynan code maydonini tekshirish ishonchli — matn Node versiyasi bilan o'zgarishi mumkin. Fayl tizimi xatolarini fs moduli darsida chuqurroq ko'ramiz.

4.3 Non-blocking — tezroq degani emas

Ko'p o'quvchi shunday o'ylaydi: "asinxron o'qish tezroq". Kanon buni o'lchadi. 200 MB fayl ikki usulda ikki martadan o'qildi. Har o'qish paytida 1 ms li setInterval "yurak urishi" sanab turdi — u kutish vaqtida event loop necha marta ishlay olganini ko'rsatadi:

js
import { readFileSync } from "node:fs";
import { readFile } from "node:fs/promises";

async function olchov(nom, ish) {
  let urish = 0;
  const t = setInterval(() => urish++, 1);
  await new Promise((r) => setTimeout(r, 5));
  urish = 0;
  const s = performance.now();
  await ish();
  const ms = performance.now() - s;
  await new Promise((r) => setImmediate(r));
  clearInterval(t);
  console.log(`${nom}: ${ms.toFixed(0)} ms, taymer ${urish} marta`);
}
await olchov("readFileSync", async () => readFileSync("katta.bin"));
await olchov("readFile    ", () => readFile("katta.bin"));

Kanon chiqishi (Node 24.21.0, Windows 11, i5-12500H, 200 MB fayl; kanon skriptida xabar uzunroq):

text
readFileSync 200 MB: 128 ms, shu vaqtda taymer 0 marta ishladi
readFile    200 MB: 149 ms, shu vaqtda taymer 149 marta ishladi
readFileSync 200 MB: 144 ms, shu vaqtda taymer 1 marta ishladi
readFile    200 MB: 147 ms, shu vaqtda taymer 141 marta ishladi

Bu blok nocheck: vaqt va sanoq har ishga tushirishda boshqacha chiqadi. setImmediate — "joriy aylanishdan keyin" degani, uni Microtask navbati, process.nextTick va setImmediate darsida o'rganamiz. Grafikda taymer sanog'ini ko'ring:

200 MB o'qish paytida taymer necha marta ishladi
  • readFileSync, 1128 ms0 marta
  • readFile, 1149 ms149 marta
  • readFileSync, 2144 ms1 marta
  • readFile, 2147 ms141 marta

Manba: IlmHamroh kanon o'lchovi, 2026-10-23: Node 24.21.0, Windows 11, i5-12500H; setInterval 1 ms

Ikki xulosa:

  1. Vaqt deyarli bir xil: 128–144 ms va 147–149 ms. Asinxron o'qish hatto biroz sekinroq — natijani thread'lar orasida uzatish ham vaqt oladi.
  2. Thread bandligi butunlay boshqacha: sinxron o'qishda event loop 0–1 marta, asinxronda 141–149 marta ishlay oldi. Ya'ni asinxron o'qish paytida server deyarli har millisekundda boshqa ishni qila oldi.

Non-blocking'ning foydasi — bitta ishni tezlashtirish emas, ko'p ishni bir vaqtda kutish.

5. Ko'p mijoz: C10k muammosi

5.1 Bitta kutish va ko'p kutish

Endi saytda jonli ko'ramiz. Uchta mijozning har biri 100 ms "I/O" kutadi (setTimeout bilan taqlid qilamiz). Avval ularni navbat bilan, keyin bir vaqtda xizmat qilamiz. Promise.all ni Ketma-ket va parallel bajarish darsida o'rgangansiz:

js
const kut = (ms) => new Promise((r) => setTimeout(r, ms));

async function sorovgaJavobBer(mijoz) {
  await kut(100); // baza yoki fayl javobini kutish
  return `${mijoz}: tayyor`;
}

const mijozlar = ["Ali", "Malika", "Jasur"];
const yaxlitla = (ms) => Math.round(ms / 100) * 100;

let boshi = Date.now();
for (const m of mijozlar) await sorovgaJavobBer(m);
console.log("Navbat bilan:", yaxlitla(Date.now() - boshi), "ms");

boshi = Date.now();
await Promise.all(mijozlar.map(sorovgaJavobBer));
console.log("Bir vaqtda:", yaxlitla(Date.now() - boshi), "ms");

Konsolda:

text
Navbat bilan: 300 ms
Bir vaqtda: 100 ms

Vaqtni 100 ga yaxlitladik — taymer bir necha millisekund kechikishi mumkin. Ikkinchi holatda uchta kutish ustma-ust tushdi. Thread hech qayerda band emas edi — u faqat uchta "tayyor bo'lsa, chaqir" topshirig'ini qoldirdi. Mijoz 3 emas, 3 000 bo'lsa ham rasm o'zgarmaydi: hamma kutish bir vaqtda o'tadi.

5.2 C10k nima

1999-yilda dasturchi Dan Kegel "The C10K problem" nomli maqola yozdi. Savol: bitta server 10 000 ta bir vaqtdagi ulanishga (Concurrent 10K) qanday xizmat qiladi?

O'sha davrdagi odatiy yondashuv — har ulanishga bitta thread. Mijoz keldi — yangi thread ochiladi, u blocking I/O bilan bemalol kutadi. Muammo: har thread o'z xotirasini (stekini) oladi, operatsion tizim esa minglab thread orasida almashishga vaqt sarflaydi. 10 000 thread — ko'p server uchun og'ir yuk edi.

Javob — event-driven (hodisaga asoslangan) yondashuv. Bitta thread ko'p ulanishni kuzatadi va faqat "tayyor bo'ldi" hodisasi kelganda ishlaydi. Operatsion tizimlarda buning uchun maxsus mexanizmlar bor: Linux'da epoll, macOS'da kqueue, Windows'da IOCP. libuv aynan shularni bitta umumiy API ortiga yashiradi. Nginx ham, Node ham shu g'oya ustiga qurilgan.

Har ulanishga thread Event loop (Node)
Kutish thread bloklanadi thread bo'sh
10 000 ulanish 10 000 thread 1 JS thread + libuv
Kod uslubi oddiy, ketma-ket asinxron (callback, await)
Zaif joyi xotira, almashish og'ir hisob hammani to'xtatadi

Oxirgi qatorga e'tibor bering. Node'ning kuchli tomoni — I/O ko'p, hisob kam bo'lgan ish: API, chat, proksi. Og'ir hisob bitta thread'ni band qilib, hammani kuttiradi. Buni mashqlarda o'zingiz ko'rasiz, chuqur esa Event loop'ni bloklash darsida.

Tekshirib ko'ring: Node serverda 1 000 foydalanuvchi bor. Har so'rov bazadan 50 ms javob kutadi va 1 ms JavaScript hisobi qiladi. Server sekinlashishining asosiy xavfi nimada: kutishdami yoki hisobdami?

Javob

Kutish xavfli emas — non-blocking bo'lsa, 1 000 ta 50 ms kutish ustma-ust o'tadi. Xavf hisobda: har so'rov thread'ni 1 ms band qiladi, 1 000 so'rov — 1 soniya sof JS vaqti, ular navbat bilan bajariladi. Hisob 1 ms dan 100 ms ga oshsa (masalan, katta JSON bilan ishlash), server ancha sekinlashadi. Agar kutish ...Sync bilan qilinsa — eng yomon holat: 50 ms ham bloklanadi.

6. readFileSync qachon joiz

6.1 Server va skript

readFileSync "taqiqlangan" funksiya emas. Savol — kim kutib qoladi:

Holat Sync joizmi Nega
Server so'rovini qayta ishlash yo'q boshqa mijozlar kutadi
Server ishga tushishida sozlamani o'qish ha hali hech kim ulanmagan
Bir martalik skript, CLI buyrug'i ha bitta foydalanuvchi, bitta ish
Boshqalar ishlatadigan kutubxona yo'q u qayerda chaqirilishini bilmaysiz

CLI'da (bitta odam, bitta buyruq) parallel kutadigan boshqa ish yo'q. readFileSync kodi esa qisqaroq. Shuning uchun ko'p CLI vositalarida sinxron fayl o'qishni uchratasiz.

6.2 CLI qadami: valyuta qaysi birini tanlaydi

Bu darsda valyuta ga kod qo'shilmaydi va commit yo'q — loyiha Node process obyekti darsida boshlanadi. Lekin qarorni hozir qabul qilamiz.

Kanonda valyuta ning barcha fayl ishlari node:fs/promises da: kesh o'qish (readFile), xavfsiz yozish (writeFile, rename), sozlama fayli. Bitta ham ...Sync chaqiruvi yo'q. Sabablari:

  • valyuta faqat CLI emas. Qism oxirida u kutubxona sifatida ham eksport qilinadi — boshqa dasturchi uning kurs funksiyalarini o'z serverida chaqirishi mumkin. Jadvaldagi oxirgi qator: kutubxonada Sync — yo'q.
  • CLI bir vaqtda bir nechta ishni kutadi: tarmoqdan kurs olish va diskdan keshni tekshirish. Non-blocking bilan ular ustma-ust o'tadi.
  • Promise shakli try/catch va await bilan o'qilishi oson — xato yo'li ham bir xil.

Narxi: await ko'payadi, funksiyalar async bo'ladi. Kanon o'lchovida yakuniy valyuta ning sovuq starti ~100 ms (bo'sh Node 73 ms) — asinxron fayl API bu raqamga sezilarli ta'sir qilmagan.

7. Ko'p uchraydigan xatolar

7.1 await siz Promise

js
import { readFile } from "node:fs/promises";
import { writeFileSync } from "node:fs";

writeFileSync("kurs.txt", "USD 11903.15");
const matn = readFile("kurs.txt", "utf8");
console.log(matn.toUpperCase());

Konsolda:

text
TypeError: matn.toUpperCase is not a function

Tarjimasi: "matn.toUpperCase funksiya emas". readFile matn emas, Promise qaytardi — Promise'da toUpperCase metodi yo'q. Xato toUpperCase ni ko'rsatadi, lekin aybdor bir qator yuqorida: await tushib qolgan. Tuzatish: const matn = await readFile(...).

7.2 Callback ichida natijani tashqariga "olib chiqish"

js
import { readFile, writeFileSync } from "node:fs";

writeFileSync("kurs.txt", "USD 11903.15");
let kurs;
readFile("kurs.txt", "utf8", (xato, matn) => {
  kurs = matn;
});
console.log("Kurs:", kurs);

Konsolda:

text
Kurs: undefined

Bu xatoni Sinxron va asinxron kod darsida brauzerda ko'rgan edingiz — Node'da ham xuddi shunday. console.log callback'dan oldin bajariladi. Natija bilan ishlaydigan kod callback ichida yoki await dan keyin bo'lishi kerak.

7.3 Serverda Sync ni "kichik fayl" deb oqlash

"Fayl atigi 1 KB, readFileSync tez" — bitta so'rov uchun to'g'ri. Lekin sekundiga 1 000 so'rovda har biri bir oz bloklaydi, disk band bo'lsa yoki fayl tarmoq diskida tursa — ancha ko'p. Serverdagi so'rov kodida qoida oddiy: Sync yo'q.

7.4 Non-blocking'ni og'ir hisobga yechim deb o'ylash

async funksiya ichiga katta siklni qo'yish uni non-blocking qilmaydi. async faqat await nuqtalarida thread'ni bo'shatadi. Sikl ichida await bo'lmasa, u boshidan oxirigacha thread'ni band qiladi. Og'ir hisob uchun boshqa yo'llar bor — ularni shu qismda o'rganamiz.

8. Mashqlar

Mashqlar kurs/mashqlar/24/02-blocking-io/ da.

1-mashq (oson): bo'sh joylarni to'ldiring

  • Natija kelguncha thread'ni to'xtatib turadigan chaqiruv — .
  • Node'da bloklovchi fayl funksiyalari nomi shu so'z bilan tugaydi — .
  • Promise qaytaradigan fayl API'si moduli — node:fs/.
  • "Bunday fayl yo'q" degan tizim xatosi kodi — .
  • 10 000 ta bir vaqtdagi ulanish muammosi — .
Yechim

Blocking — thread natijani kutib turadi. Sync — readFileSync, writeFileSync, existsSync. node:fs/promises — readFile, writeFile Promise bilan. ENOENT — "Error NO ENTry". C10k — 1999-yilgi Dan Kegel maqolasi, event-driven server g'oyasining manbai.

2-mashq (o'rta): tartibni oldindan ayting

Kodni ishga tushirmasdan, qatorlar qaysi tartibda chiqishini yozing. Keyin Node'da tekshiring:

js
import { readFileSync, writeFileSync } from "node:fs";
import { readFile } from "node:fs/promises";

writeFileSync("buyurtma.txt", "osh");
console.log("A");
readFile("buyurtma.txt", "utf8").then((m) => console.log("B", m));
console.log("C", readFileSync("buyurtma.txt", "utf8"));
console.log("E");
Yechim
text
A
C osh
E
B osh

A — oddiy sinxron qator. readFile o'qishni boshlab, darhol Promise qaytaradi — B hali chiqmaydi. readFileSync faylni o'qiguncha kutadi va natija bilan C osh chiqadi. E — yana sinxron. Butun sinxron kod tugagach, event loop o'qish tugaganini ko'radi va .then ichidagi B osh ishlaydi. Qiziq tomoni: readFileSync keyin chaqirilgan bo'lsa ham, natijasini birinchi berdi — u kutib turdi, readFile esa kutmadi.

3-mashq (qiyin): bitta og'ir mijoz hammani kuttiradi

Server modelini yozing. xizmatQil(mijoz, ogir, boshi) funksiyasi:

  1. 100 ms I/O kutadi (kut(100) — «Bitta kutish va ko'p kutish» bo'limidagi kabi).
  2. Agar ogir bo'lsa — 200 ms og'ir hisob qiladi («Bloklangan taymer» bo'limidagi bo'sh sikl kabi while).
  3. boshi dan beri o'tgan vaqtni 100 ga yaxlitlab, "Ali: 100 ms" ko'rinishida qaytaradi.

kunniOtkaz(ogirMijoz) funksiyasi Ali, Malika, Jasur uchun xizmatQil ni Promise.all bilan bir vaqtda chaqirsin va natijalarni , bilan bitta qatorda chiqarsin. Ikki marta chaqiring: kunniOtkaz(null) va kunniOtkaz("Ali"). Ishga tushirishdan oldin taxmin qiling: ikkinchi holatda Malika va Jasur necha ms kutadi?

Yechim
js
const kut = (ms) => new Promise((r) => setTimeout(r, ms));

function ogirHisob(ms) {
  const boshi = Date.now();
  while (Date.now() - boshi < ms) {} // CPU band — ataylab
}

async function xizmatQil(mijoz, ogir, boshi) {
  await kut(100); // I/O kutish (baza, fayl)
  if (ogir) ogirHisob(200);
  const ms = Math.round((Date.now() - boshi) / 100) * 100;
  return `${mijoz}: ${ms} ms`;
}

async function kunniOtkaz(ogirMijoz) {
  const boshi = Date.now();
  const mijozlar = ["Ali", "Malika", "Jasur"];
  const natijalar = await Promise.all(
    mijozlar.map((m) => xizmatQil(m, m === ogirMijoz, boshi)),
  );
  console.log(natijalar.join(", "));
}

await kunniOtkaz(null);
await kunniOtkaz("Ali");

Konsolda:

text
Ali: 100 ms, Malika: 100 ms, Jasur: 100 ms
Ali: 300 ms, Malika: 300 ms, Jasur: 300 ms

Birinchi qatorda uchala kutish ustma-ust o'tdi — hammasi 100 ms. Ikkinchi qatorda faqat Ali'ning so'rovi og'ir edi. Lekin Malika va Jasur ham 300 ms kutdi. Ularning 100 ms I/O'si allaqachon tugagan edi, callback'lari navbatda turardi. Thread esa Ali'ning hisobi bilan band edi. Bu — Node'ning asosiy zaif joyi: bitta og'ir so'rov hammani kuttiradi. Kutish (I/O) bo'shatiladi, hisob (CPU) bo'shatilmaydi.

4-mashq: CLI qadami — sync yoki async?

valyuta uchun to'rtta vaziyat. Har biriga Sync joizmi yoki fs/promises kerakmi — javob bering va bir gap bilan asoslang:

  1. valyuta --version buyrug'i package.json dan versiyani bir marta o'qiydi va chiqadi.
  2. kursOl(kod) funksiyasi keshni o'qiydi; u paketdan eksport qilinadi va boshqalar serverda chaqirishi mumkin.
  3. Siz o'zingiz uchun bir martalik skript yozyapsiz: eski kesh fayllarini sanab chiqadi.
  4. Kelajakda valyuta ga HTTP server qo'shiladi va har so'rovda kesh fayli o'qiladi.
Yechim
  1. Sync joiz — bitta buyruq, kutadigan boshqa ish yo'q. Kanon esa izchillik uchun bu yerda ham Promise shaklini tanlagan — ikkalasi to'g'ri.
  2. fs/promises — kutubxona kodi, qayerda chaqirilishini bilmaysiz. Serverda chaqirilsa, Sync hammani bloklaydi.
  3. Sync joiz — bir martalik skript, foydalanuvchi siz o'zingiz.
  4. fs/promises — server so'rovida Sync yo'q. HTTP serverni 25-qismda yozasiz; u yerda ham xuddi shu qoida.

Umumiy qoida: "mendan boshqa kim kutib qolishi mumkin?" deb so'rang. Hech kim — Sync joiz. Boshqa so'rovlar yoki bilmagan chaqiruvchi — non-blocking.

9. Real ishda

  • Kod review: server kodida readFileSync, execSync, pbkdf2Sync kabi chaqiruvlar — tajribali dasturchi birinchi ko'radigan joy. ESLint'da buni ushlaydigan qoidalar ham bor.
  • Qayerda Node kuchli: API serverlar, real vaqt chat va bildirishnomalar, proksi va gateway'lar, fayl yuklash — I/O ko'p, hisob kam. Rasm yoki video qayta ishlash, katta hisob-kitob — og'ir CPU ishi, uni alohida thread yoki xizmatga chiqarish kerak.
  • Intervyu savollari: "Blocking va non-blocking farqi?", "Node bitta thread bilan qanday qilib ko'p so'rovga xizmat qiladi?", "readFileSync ni qachon ishlatish mumkin?", "C10k muammosi nima?". Kuchli javob kutish va hisobni ajratadi.
  • Ishlab chiqarishda: "server sekin" shikoyatida event loop kechikishini o'lchash odatiy amaliyot — bu raqam thread qanchalik band ekanini ko'rsatadi. Uni Event loop'ni bloklash darsida o'lchaymiz.

Xulosa

  • Blocking chaqiruv natija kelguncha JS thread'ini ushlab turadi; non-blocking ishni topshirib darhol qaytadi.
  • Node'da bir ishning uch shakli bor: readFileSync (bloklaydi), callback va node:fs/promises (bloklamaydi); asosiy tanlov — Promise shakli.
  • Asinxron o'qish tezroq emas (200 MB: 147–149 ms vs 128–144 ms), lekin kutish paytida event loop 141–149 marta ishlay oldi, sinxronda — 0–1 marta.
  • C10k: har ulanishga thread o'rniga bitta event loop; Node'ning kuchi — I/O, zaif joyi — og'ir hisob.
  • Sync CLI va ishga tushish vaqtida joiz; server so'rovida va kutubxonada — yo'q.

Keyingi dars: Event loop fazalari chuqur — Node'dagi event loop'ning oltita fazasi va qaysi callback qachon ishlashi.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Blocking va non-blocking I/O: Node bitta thread bilan qanday ulguradi — IlmHamroh