İçeriğe geç
academia.sh

Ders 02 / 20

Olay Döngüsü Aşamaları

Döngünün aşama sırası, zamanlayıcı ile denetim aşaması arasındaki fark, iki mikro görev kuyruğunun boşaltılma anı ve engelleyen işin ölçülebilir bedeli.

İçindekiler

Önceki ders dosyayı readFileSync ile okudu: çağrı döndüğünde veri hazırdı, ama çağrı dönene kadar süreçte başka hiçbir iş ilerlemedi. Tek iş parçacıklı bir sunucuda bu davranış kabul edilebilir değildir; bekleyen her milisaniye, aynı anda gelen bütün isteklerin gecikmesidir.

Bu dersin sorusu şudur: bekleme yerine geçen düzenek tam olarak ne yapar? Eşzamansız JavaScript ve Çalışma Zamanı kursunda olay döngüsü, çağrı yığıtı boşaldığında kuyruktan görev alan bir çevrim olarak tanıtılmıştı. Sunucu çalışma zamanında bu çevrimin içi görünür: tek bir kuyruk değil, sırayla ziyaret edilen birkaç aşama vardır.

Döngünün Aşamaları

Süreç, ana modülün gövdesini baştan sona çalıştırdıktan sonra olay döngüsüne girer. Döngünün her turu, sabit sırada birkaç aşamadan geçer. Her aşamanın kendi geri çağrı kuyruğu vardır; aşamaya girildiğinde o kuyruk boşaltılır, sonra bir sonraki aşamaya geçilir.

Aşama Çalıştırdığı geri çağrılar
zamanlayıcı süresi dolmuş setTimeout ve setInterval geri çağrıları
bekleyen geri çağrılar bir önceki turdan ertelenmiş sistem düzeyi geri çağrılar
yoklama tamamlanan girdi/çıktı işlemlerinin geri çağrıları
denetim setImmediate ile kurulan geri çağrılar
kapanma close olaylarının geri çağrıları

Yoklama (poll) aşaması, döngünün asıl beklediği yerdir. Çalıştırılacak başka iş yoksa süreç burada durur ve işletim sisteminden “hazır olan girdi/çıktı var mı?” diye sorar. Bu bekleme, işlemciyi meşgul eden bir bekleme değildir; çekirdek, bir soket okunabilir hâle geldiğinde ya da bir dosya okuması bittiğinde süreci uyandırır.

Böylece “beklemek” ile “engellemek” ayrışır. Süreç bir isteğin yanıtını beklerken yoklama aşamasında uyuyor olabilir; bu sırada başka bir bağlantıdan veri gelirse uyanır ve onun geri çağrısını çalıştırır.

Zamanlayıcı ile Denetim Aşamasının Sırası

Bir aşamanın kuyruğuna, o aşamadan sonra gelen bir aşamanın içindeyken ekleme yapılırsa, geri çağrı aynı turda çalışır; önce gelen bir aşamanın kuyruğuna ekleme yapılırsa sonraki turu bekler. Bu kural, girdi/çıktı geri çağrısının içinden bakınca kesin bir sıra üretir:

// asamalar.mjs
import { readFile } from 'node:fs';

console.log('senkron govde');

readFile('olcumler.ndjson', () => {
  console.log('yoklama: dosya okundu');

  process.nextTick(() => console.log('  nextTick kuyrugu'));
  Promise.resolve().then(() => console.log('  soz mikro gorevi'));
  setTimeout(() => console.log('  zamanlayici asamasi'), 0);
  setImmediate(() => console.log('  denetim asamasi'));

  console.log('yoklama: geri cagri govdesi bitti');
});
node asamalar.mjs
senkron govde
yoklama: dosya okundu
yoklama: geri cagri govdesi bitti
  nextTick kuyrugu
  soz mikro gorevi
  denetim asamasi
  zamanlayici asamasi

Sıra rastlantı değildir. Geri çağrı yoklama aşamasında çalıştı; denetim aşaması yoklamadan sonra geldiği için setImmediate aynı turda çalıştı. Zamanlayıcı aşaması ise yoklamadan önce geldiğinden setTimeout bir sonraki turu bekledi. Bu ilişki, girdi/çıktı geri çağrısı içinde her zaman böyledir.

Aynı iki çağrı ana modülün gövdesinden yapıldığında sıra güvence altında değildir:

// yaris.mjs
setTimeout(() => console.log('zamanlayici'), 0);
setImmediate(() => console.log('denetim'));

Aşağıdaki iki çıktı, aynı dosyanın ardışık çalıştırmalarından alınmıştır:

node yaris.mjs
denetim
zamanlayici
node yaris.mjs
zamanlayici
denetim

Gerekçe, döngünün ilk turuna girildiğinde geçen sürededir. setTimeout için verilen sıfır, çalışma zamanı tarafından en az bir milisaniyeye yuvarlanır. Ana modülün çalışması bir milisaniyeden kısa sürerse döngü zamanlayıcı aşamasına girdiğinde süre henüz dolmamıştır ve geri çağrı bir sonraki tura kalır; uzun sürerse süre dolmuş olur ve geri çağrı ilk turda çalışır. Süre makinenin yüküne bağlı olduğundan sonuç çalıştırmadan çalıştırmaya değişir. Buradan çıkan kural şudur: iki farklı aşamaya ait geri çağrıların göreli sırasına program mantığı bağlanmaz.

İki Mikro Görev Kuyruğu

Aşamalar arasında ve her geri çağrıdan sonra, iki ek kuyruk boşaltılır. Bunlar bir aşamaya ait değildir; aşamalar arasındaki her denetim noktasında ele alınırlar.

  • process.nextTick kuyruğu, çalışma zamanına özgüdür.
  • Söz mikro görev kuyruğu, dilin standardında tanımlıdır ve tarayıcıda da bulunur.

Bir denetim noktasında önce nextTick kuyruğu, sonra söz kuyruğu boşaltılır. Yukarıdaki çıktıda bu sıra görülüyor: geri çağrı gövdesi bittikten sonra önce nextTick kuyrugu, ardından soz mikro gorevi yazıldı.

Bu sıranın modülün en üst düzeyinde farklı göründüğü bir durum vardır:

// mikro.cjs
console.log('1 senkron');
process.nextTick(() => console.log('2 nextTick'));
Promise.resolve().then(() => console.log('3 mikro gorev'));
console.log('4 senkron son');
node mikro.cjs
1 senkron
4 senkron son
2 nextTick
3 mikro gorev

Aynı satırlar .mjs uzantısıyla kaydedildiğinde sıra tersine döner:

node mikro.mjs
1 senkron
4 senkron son
3 mikro gorev
2 nextTick

Nedeni, ES modüllerinin değerlendirilme biçimidir: modül gövdesinin kendisi bir söz işi olarak çalışır. Gövde bittiğinde süreç hâlâ söz kuyruğunu boşaltmaktadır, bu yüzden yeni eklenen söz işi aynı boşaltma turunda çalışır; nextTick kuyruğu ise turun sonundaki denetim noktasını bekler. Ders şu: iki kuyruğun göreli sırası bir sözleşme değildir, kodun nerede çalıştığına bağlıdır. Programın doğruluğu bu sıraya dayanmamalıdır.

process.nextTick kuyruğunun ayrıca bir tehlikesi vardır. Bu kuyruk, bir sonraki aşamaya geçilmeden tamamen boşaltılır; bir geri çağrı her çalıştığında kuyruğa yenisini eklerse döngü hiçbir zaman ilerleyemez ve süreç yanıt vermeyi bırakır.

Engellemenin Ölçülen Bedeli

Aşama modelinin tek bir zayıf noktası vardır: bir aşamanın geri çağrısı çalışırken başka hiçbir şey çalışamaz. Uzun süren senkron iş, döngünün tamamını durdurur.

// engelleme.mjs
const baslangic = process.hrtime.bigint();

setTimeout(() => {
  const gecen = Number(process.hrtime.bigint() - baslangic) / 1e6;
  console.log(`10 ms icin kurulan zamanlayici ${gecen.toFixed(0)} ms sonra calisti`);
}, 10);

// Olay dongusunu bilerek mesgul eden senkron is
const bitis = Date.now() + 200;
while (Date.now() < bitis) { /* bos dongu */ }
console.log('senkron is bitti');
node engelleme.mjs
senkron is bitti
10 ms icin kurulan zamanlayici 200 ms sonra calisti

Yazdırılan süre makineye ve yüke göre birkaç milisaniye oynar; belirleyici olan büyüklüktür. Zamanlayıcı için verilen 10 ms bir söz değil bir alt sınırdır: geri çağrı, süresi dolduktan sonra döngü zamanlayıcı aşamasına ulaştığında çalışır. Döngü 200 ms boyunca meşgul edildiği için gecikme de 200 ms olmuştur.

Aynı ölçü bir sunucuda şu anlama gelir: her istek başına 200 ms senkron iş yapan bir işleyici, saniyede en çok beş isteğe yanıt verir ve altıncı istek kuyrukta bekler. Bu sınır donanımdan değil, işin yürütülme biçiminden gelir.

Buradan iki kural çıkar. Birincisi, girdi/çıktı için eşzamanlı arayüzler (...Sync ile biten çağrılar) sunucu isteği işlerken kullanılmaz; başlangıç anındaki yapılandırma okuması gibi, döngü henüz çalışmıyorken yapılan işler ayrıcalıklıdır. İkincisi, işlemciye bağlı uzun hesaplar ana iş parçacığından çıkarılmalıdır; bunun yolu Küme ve İşçi İş Parçacıkları dersinde ele alınacak.

Özet

  • Olay döngüsünün her turu sabit sırada aşamalardan geçer: zamanlayıcı, bekleyen geri çağrılar, yoklama, denetim, kapanma. Her aşamanın kendi kuyruğu vardır.
  • Yoklama aşaması, işlemciyi meşgul etmeyen bekleme noktasıdır; çekirdek, hazır olan girdi/çıktı için süreci uyandırır.
  • Girdi/çıktı geri çağrısı içinde setImmediate aynı turda, setTimeout bir sonraki turda çalışır. Ana modül gövdesinde bu sıra güvence altında değildir.
  • nextTick ve söz kuyrukları aşamalar arasındaki denetim noktalarında boşaltılır; göreli sıraları kodun nerede çalıştığına bağlıdır ve program mantığına dayanak yapılmaz.
  • Zamanlayıcı gecikmesi alt sınırdır; döngüyü meşgul eden senkron iş, gecikmeyi doğrudan kendi süresi kadar büyütür.

Sonraki Adım

Bu derste süreç, döngüyü çalıştıran bir kap olarak ele alındı. Sürecin kendisi de programa açık bir nesnedir: hangi argümanlarla başlatıldığını, hangi ortam değişkenlerini devraldığını, standart akışlarının nereye bağlı olduğunu ve hangi çıkış koduyla sonlanacağını bu nesne üzerinden okur ve yazarsınız. Sonraki ders süreç nesnesini ele alır ve ölçüm toplayıcının ilk komut satırı davranışını kurar.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat