Node.js, asenkron yapısıyla bilinen ve olay güdümlü (event-driven) bir çalışma modeline sahip olan popüler bir JavaScript çalışma ortamıdır. Bu yapının merkezinde ise event loop (olay döngüsü) bulunur. Event loop, çeşitli kuyruklardan gelen görevleri belirli bir sıraya göre işleyerek Node.js'in yüksek performanslı ve ölçeklenebilir olmasını sağlar. Bu makalede, Node.js'teki I/O queue (giriş/çıkış kuyruğu) ve diğer önemli kuyruklar hakkında detaylı bir inceleme yapacağız.
Node.js'in etkin ve hızlı çalışmasının arkasındaki anahtar bileşen event loop'tur. Event loop, senkron ve asenkron işlemlerin işlenme sırasını düzenleyen bir döngüdür. Bu döngüde çeşitli kuyruklar bulunur ve her kuyruğun farklı bir önceliği vardır:
Senkron Kod: İlk olarak çalıştırılan kod parçasıdır. Bu kod doğrudan çalıştırılır ve bloklama potansiyeline sahiptir.
Mikro Görev Kuyrukları (Micro Task Queues): process.nextTick ve Promise.resolve gibi işlemlerle eklenen görevler bu kuyrukta yer alır. Mikro görevler, senkron koddan hemen sonra çalıştırılır ve yüksek önceliğe sahiptir.
Zamanlayıcı Kuyruğu (Timer Queue): setTimeout ve setInterval gibi zamanlayıcılarla eklenen geri çağırma fonksiyonları bu kuyrukta toplanır. Belirtilen gecikme süresi tamamlandığında bu kuyruktaki görevler çalıştırılır.
I/O Kuyruğu (I/O Queue): Dosya okuma/yazma, ağ işlemleri gibi I/O operasyonları tamamlandığında geri çağırma fonksiyonları bu kuyrukta yer alır ve zamanlayıcı kuyruğundan sonra çalıştırılır.
I/O queue, asenkron giriş/çıkış işlemleri tamamlandığında çalıştırılacak geri çağırma fonksiyonlarını barındıran bir kuyruktur. Node.js'te birçok asenkron fonksiyon, I/O queue'ya geri çağırma fonksiyonları ekler. Örneğin, fs modülünden readFile fonksiyonu kullanıldığında, dosya okuma işlemi tamamlandığında geri çağırma fonksiyonu I/O queue'ya eklenir.
Node.js'teki kuyruklar arasında belirli bir öncelik sırası vardır. Bu öncelik sırası, görevlerin ne zaman çalıştırılacağını belirler ve uygulamanın performansını optimize eder.
Bir Node.js uygulamasında aşağıdaki kod parçacığını ele alalım:
const fs = require('fs');
fs.readFile(__filename, (err, data) => {
console.log('Read File Callback');
});
process.nextTick(() => {
console.log('Next Tick Callback');
});
Promise.resolve().then(() => {
console.log('Promise Callback');
});
setTimeout(() => {
console.log('Set Timeout Callback');
}, 0);
Bu kodda, dört asenkron işlem bulunmaktadır:
fs.readFile: Dosya okuma işlemi tamamlandığında I/O queue'ya geri çağırma ekler.
process.nextTick: Mikro görev kuyruğuna geri çağırma ekler.
Promise.resolve: Mikro görev kuyruğuna geri çağırma ekler.
setTimeout: Zamanlayıcı kuyruğuna geri çağırma ekler.
Bu işlemler event loop'ta aşağıdaki sıraya göre çalıştırılacaktır:
İlk olarak senkron kod çalıştırılır.
Mikro görev kuyruğundaki geri çağırmalar çalıştırılır (process.nextTick ve Promise.resolve).
Zamanlayıcı kuyruğundaki geri çağırmalar çalıştırılır (setTimeout).
Son olarak, I/O queue'daki geri çağırmalar çalıştırılır (fs.readFile).
Çıktı şu şekilde olacaktır:
Next Tick Callback
Promise Callback
Set Timeout Callback
Read File Callback
Node.js'te zamanlayıcı ve I/O kuyrukları arasındaki öncelik bazen tutarsız olabilir. Bu tutarsızlık, zamanlayıcıların minimum gecikme süresinin 1 milisaniye olarak ayarlanmasından kaynaklanır. Örneğin, setTimeout ile 0 milisaniye gecikme ayarlansa bile, bu gecikme süresi 1 milisaniyeye ayarlanır. Bu durum, zamanlayıcı geri çağırmasının I/O geri çağırmasından önce veya sonra çalışmasına neden olabilir.
const fs = require('fs');
fs.readFile(__filename, (err, data) => {
console.log('Read File Callback');
});
setTimeout(() => {
console.log('Set Timeout Callback');
}, 0);
Bu kodda, çıktının her çalıştırmada farklı olabileceği gözlemlenebilir:
Read File Callback
Set Timeout Callback
veya
Set Timeout Callback
Read File Callback
Bu belirsizlik, zamanlayıcı kuyruğunun minimum gecikme süresinden kaynaklanır ve CPU'nun ne kadar meşgul olduğuna bağlı olarak değişebilir.
I/O queue, event loop'ta zamanlayıcı kuyruğundan sonra çalıştırılır. Bu, asenkron I/O işlemlerinin zamanlayıcılardan sonra işlendiği anlamına gelir. Mikro görev kuyruğu ve zamanlayıcı kuyruğu boşaldıktan sonra I/O queue'daki görevler çalıştırılır.
Örnek:
const fs = require('fs');
fs.readFile(__filename, (err, data) => {
console.log('Read File Callback');
});
process.nextTick(() => {
console.log('Next Tick Callback');
});
Promise.resolve().then(() => {
console.log('Promise Callback');
});
setTimeout(() => {
console.log('Set Timeout Callback');
}, 0);
Bu kodda, event loop aşağıdaki sırayla çalışacaktır:
Next Tick Callback
Promise Callback
Set Timeout Callback
Read File Callback
Çıktı sırası:
Next Tick Callback
Promise Callback
Set Timeout Callback
Read File Callback
Node.js'in event loop mekanizması ve çeşitli kuyrukların işleyişi, yüksek performanslı ve ölçeklenebilir uygulamalar geliştirmeyi mümkün kılar. I/O queue, asenkron I/O işlemlerinin işlenmesini sağlar ve mikro görevler ile zamanlayıcı görevlerinden sonra çalıştırılır. Bu öncelik sırası, uygulamanın etkinliğini artırır ve görevlerin doğru zamanda işlenmesini sağlar. Node.js'in asenkron yapısını ve event loop'un işleyişini anlamak, geliştiricilere daha verimli ve etkili kod yazma becerisi kazandırır.