Blocking vs Non-blocking — nima uchun xavfli
Muammodan boshlaylik
Siz Node.js da server yozdingiz. Bitta endpoint bor — foydalanuvchi ma'lumotlarini qaytaradi. 100 ta foydalanuvchi bir vaqtda so'rov yubordi. Birinchi so'rov 3 soniya ishlaydi — qolgan 99 ta kutib turadi. Server "muzlab" qoldi.
Bu — blocking ning oqibati. Node.js da bu xato juda keng tarqalgan va topish qiyin, chunki kichik loyihalarda sezilmaydi. Faqat yuklanish oshganda ko'rinadi.
Blocking nima?
Blocking — Call Stack band bo'lib, Event Loop keyingi callbacklarni bajara olmaydigan holat.
Node.js bitta thread da ishlaydi. Agar shu thread band bo'lsa — boshqa hech narsa ishlamaydi. HTTP so'rovlar, taymerlar, I/O callbacklar — hammasi kutadi.
const http = require('http');
http.createServer((req, res) => {
if (req.url === '/tez') {
res.end('Tez javob');
return;
}
if (req.url === '/sekin') {
// ❌ Blocking — 5 soniya davomida hech narsa ishlamaydi
const boshlanish = Date.now();
while (Date.now() - boshlanish < 5000) {}
res.end('Sekin javob');
}
}).listen(3000);
/sekin ga so'rov yuborilganda — 5 soniya davomida /tez ga yuborilgan so'rovlar ham javob olmaydi. Bitta foydalanuvchi butun serverni "ushlab" turadi.
Blocking turlari
1. CPU-intensive hisoblash
// ❌ Blocking — katta hisoblash
function fibonacciSekin(n) {
if (n <= 1) return n;
return fibonacciSekin(n - 1) + fibonacciSekin(n - 2);
}
app.get('/fib', (req, res) => {
const natija = fibonacciSekin(45); // bir necha soniya!
res.json({ natija });
});
Bu endpoint ishlayotganda — boshqa barcha so'rovlar kutadi.
2. Sinxron fayl o'qish
const fs = require('fs');
// ❌ Blocking
app.get('/fayl', (req, res) => {
const data = fs.readFileSync('katta-fayl.txt'); // kutadi
res.send(data);
});
// ✅ Non-blocking
app.get('/fayl', (req, res) => {
fs.readFile('katta-fayl.txt', (err, data) => {
res.send(data);
});
});
readFileSync — fayl o'qilguncha thread to'xtaydi. readFile — fayl o'qilishini OS ga topshiradi, thread bo'shaydi.
3. Sinxron JSON parse — katta ma'lumot
// ❌ Blocking — 10MB JSON
app.post('/import', (req, res) => {
const data = JSON.parse(req.body); // katta bo'lsa sekin
// ...
});
Kichik JSON uchun muammo yo'q. Lekin megabaytlik JSON parse qilish — bir necha millisekund bloklanish.
4. Sinxron kriptografiya
const crypto = require('crypto');
// ❌ Blocking
app.post('/register', (req, res) => {
const hash = crypto.pbkdf2Sync(
req.body.parol, 'salt', 100000, 64, 'sha512'
); // ~100ms bloklanish
// ...
});
// ✅ Non-blocking
app.post('/register', (req, res) => {
crypto.pbkdf2(
req.body.parol, 'salt', 100000, 64, 'sha512',
(err, hash) => {
// ...
}
);
});
Non-blocking qanday ishlaydi?
Non-blocking kod — ishni OS yoki libuv ga topshiradi va thread ni bo'shatadi. Ish tugaganda callback chaqiriladi.
Blocking:
Thread → fayl o'qi → [kutadi...] → natija → keyingi ish
Non-blocking:
Thread → "fayl o'qi, tayyor bo'lganda xabar ber" → keyingi ish
↓
[OS fayl o'qiydi]
↓
callback chaqiriladi
Vizual farq:
Blocking (1 ta so'rov):
──────[3s hisoblash]──────► javob
──────────────────────────► (2-so'rov kutdi)
──────────────────────────► (3-so'rov kutdi)
Non-blocking (3 ta so'rov):
──[DB so'rov]──────────────► javob 1
──[DB so'rov]────────────► javob 2
──[DB so'rov]──────────────────► javob 3
(hammasi parallel, thread bo'sh)
Bloklashni aniqlash
Oddiy test
const http = require('http');
http.createServer((req, res) => {
if (req.url === '/blocking') {
// Sintetik blok
const end = Date.now() + 2000;
while (Date.now() < end) {}
res.end('OK');
return;
}
res.end('Tez javob');
}).listen(3000);
Terminalda ikki oynada:
# 1-oyna
curl http://localhost:3000/blocking
# 2-oyna (darhol keyin)
curl http://localhost:3000/
# /blocking tugamaguncha bu ham kutadi!
Event Loop kechikishini o'lchash
let oxirgi = Date.now();
setInterval(() => {
const hozir = Date.now();
const kechikish = hozir - oxirgi - 100; // 100ms bo'lishi kerak edi
if (kechikish > 10) {
console.warn(`Event Loop kechikdi: ${kechikish}ms`);
}
oxirgi = hozir;
}, 100);
Agar bu 10ms dan ko'p kechikish ko'rsatsa — biror joyda blok bor.
Yechimlar
1. Asinxron versiyalarni ishlating
Node.js ning barcha sinxron funksiyalarining asinxron versiyasi bor:
// ❌ → ✅
fs.readFileSync → fs.readFile / fs.promises.readFile
fs.writeFileSync → fs.writeFile / fs.promises.writeFile
crypto.pbkdf2Sync → crypto.pbkdf2
dns.lookupSync → dns.lookup
2. CPU-intensive ishni bo'laklarga bo'lish
// ❌ Blocking — katta massivni bir yo'la qayta ishlash
function hammaniQaytaIshla(massiv) {
return massiv.map(element => ogirHisoblash(element));
}
// ✅ Non-blocking — setImmediate bilan bo'laklash
function bolaklabQaytaIshla(massiv, callback, index = 0, natija = []) {
if (index >= massiv.length) {
callback(natija);
return;
}
natija.push(ogirHisoblash(massiv[index]));
setImmediate(() => bolaklabQaytaIshla(massiv, callback, index + 1, natija));
}
3. Worker Threads — og'ir hisoblash uchun
CPU-intensive ish uchun eng to'g'ri yechim — alohida thread:
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
if (isMainThread) {
// Asosiy thread — server ishlaydi
app.get('/fib', (req, res) => {
const worker = new Worker(__filename, {
workerData: { n: 45 }
});
worker.on('message', natija => res.json({ natija }));
});
} else {
// Worker thread — og'ir hisoblash
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
parentPort.postMessage(fibonacci(workerData.n));
}
Worker thread alohida thread da ishlaydi — asosiy thread bloklanmaydi.
Qoidalar
✓ fs.readFile, fs.promises — readFileSync emas
✓ crypto.pbkdf2 — pbkdf2Sync emas
✓ DNS, tarmoq — har doim asinxron
✓ Katta JSON — stream bilan parse qiling
✓ CPU-intensive — Worker Threads ga topshiring
✗ while(true) — hech qachon asosiy threadda
✗ for (10M iteratsiya) — setImmediate bilan bo'lang
✗ Sync funksiyalar — faqat dastur boshlanishida (config o'qish)
💡 Istisno:
readFileSyncni server ishga tushayotganda config fayl o'qish uchun ishlatish mumkin — chunki bu bir marta, so'rovlar kelmasdan oldin bajariladi.
Xulosa
Blocking — Node.js ning eng xavfli muammosi. Bitta bloklovchi kod butun serverni to'xtatadi.
Node.js bitta thread da ishlaydi — shu thread band bo'lsa, hech narsa ishlamaydi. Shuning uchun:
- I/O uchun har doim asinxron versiyalar
- CPU-intensive ish uchun Worker Threads
- Katta hisoblashni
setImmediatebilan bo'laklash
Blocking kichik loyihalarda sezilmaydi — faqat yuklanish oshganda ko'rinadi. Shuning uchun boshidanoq to'g'ri yozish muhim.
Keyingi dars
10-dars: Microtask va Macrotask — qaysi birinchi ishlaydi