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.nextTickkuyruğ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
setImmediateaynı turda,setTimeoutbir sonraki turda çalışır. Ana modül gövdesinde bu sıra güvence altında değildir. nextTickve 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.