Ders 11 / 20
Alt Süreçler
Dış komut çalıştırmanın iki yolu, kabuk katmanının getirdiği risk, çıkış kodu ile sonlandıran sinyalin ayrımı ve çalışma zamanı süreçleri arasında ileti geçirme.
İçindekiler
Önceki ders hizmeti tek bir süreçte kurdu. Bir sürecin yapabileceği iş sınırlıdır ve bazı işleri hiç yapmaması gerekir: sistemde zaten var olan bir aracın işini yeniden yazmak, o aracın yıllarca sınanmış davranışını yeniden üretmeye çalışmaktır.
Bu ders sürecin dışına iş çıkarmayı ele alır. İki ayrı ihtiyaç vardır: bir dış komutu çalıştırmak ve aynı çalışma zamanından ikinci bir süreç başlatıp onunla konuşmak.
Dış Komut Çalıştırmak
node:child_process modülündeki spawn, bir programı ayrı süreç olarak başlatır ve
onun üç standart akışını nesne olarak verir. Kabuk Programlama kursundaki akış modeli
burada olduğu gibi karşılık bulur.
// alt-surec.mjs import { spawn } from 'node:child_process'; import { once } from 'node:events'; function calistir(komut, argumanlar) { const cocuk = spawn(komut, argumanlar); // kabuk yok: argumanlar dizi olarak gecer const cikti = []; const hata = []; cocuk.stdout.on('data', (p) => cikti.push(p)); cocuk.stderr.on('data', (p) => hata.push(p)); return once(cocuk, 'close').then(([kod, sinyal]) => ({ kod, sinyal, cikti: Buffer.concat(cikti).toString('utf8').trim(), hata: Buffer.concat(hata).toString('utf8').trim(), })); } console.log('wc -l :', await calistir('wc', ['-l', 'olcumler.ndjson'])); console.log('yok.txt:', await calistir('cat', ['yok.txt']));
node alt-surec.mjs
wc -l : { kod: 0, sinyal: null, cikti: '12 olcumler.ndjson', hata: '' }
yok.txt: {
kod: 1,
sinyal: null,
cikti: '',
hata: 'cat: yok.txt: No such file or directory'
}
İki çağrının ayrımı çıkış kodundadır. Sıfır başarıyı, bir başarısızlığı bildirir; Kabuk Programlama kursundaki kural değişmedi. Tanı iletisi ikinci akıştan geldi ve veriyle karışmadı — aynı ayrımın çalışma zamanı tarafındaki karşılığı budur.
Parçalar arabellek olarak toplanıp sonda birleştiriliyor. Her parçayı ayrı ayrı dizgiye çevirip eklemek, arabellekler dersinde gösterilen sınır bozulmasına yol açardı.
Kabuk Katmanı ve Argüman Aktarımı
Aynı modülde exec adlı bir fonksiyon daha vardır. Fark, komutu bir kabuk sürecine metin
olarak vermesidir. Kabuk o metni kendi kurallarıyla yorumlar: değişken açılımı,
yol adı genişletmesi, boru hattı, noktalı virgülle komut ayırma.
Bu yorum, komut metnine dışarıdan gelen bir parça eklendiğinde sorun olur:
// alt-surec-kabuk.mjs import { execFile, exec } from 'node:child_process'; import { promisify } from 'node:util'; const execSoz = promisify(exec); const execFileSoz = promisify(execFile); // Disaridan gelmis, guvenilmeyen bir dosya adi const dosyaAdi = 'olcumler.ndjson; echo ENJEKTE'; // 1. Kabugu araya sokan cagri: metin kabuk tarafindan yorumlanir const { stdout: kabukCiktisi } = await execSoz(`wc -l ${dosyaAdi}`); console.log('exec :', JSON.stringify(kabukCiktisi.trim())); // 2. Kabuk olmadan cagri: ad tek bir arguman olarak gecer try { await execFileSoz('wc', ['-l', dosyaAdi]); } catch (hata) { console.log('execFile :', hata.stderr.trim()); }
node alt-surec-kabuk.mjs
exec : "12 olcumler.ndjson\nENJEKTE" execFile : wc: olcumler.ndjson; echo ENJEKTE: open: No such file or directory
Birinci çağrıda noktalı virgülden sonraki komut da çalıştı. Dosya adı yerine
gelen metin bir hesap silme ya da veri gönderme komutu olsaydı sonuç aynı biçimde
çalıştırılırdı. İkinci çağrıda ise metnin tamamı tek bir argüman olarak geçti; wc
o adda bir dosya arayıp bulamadı.
Kural: komut metnini dizgi birleştirmesiyle kurmayın. Program adı ve argüman dizisi ayrı verildiğinde araya kabuk girmez ve yorumlanacak bir metin oluşmaz. Gerçekten kabuk özelliği gerekiyorsa — boru hattı veya yönlendirme gibi — sabit bir komut metni yazılır ve değişken bölümler ortam değişkeni olarak aktarılır.
promisify, geri çağrılı imzayı söz döndüren imzaya çeviren yerleşik yardımcıdır.
Çalışma zamanının geri çağrılı arayüzlerinin çoğu bu dönüşümü destekler.
exec ve execFile ayrıca çıktıyı bellekte biriktirir ve bir sınırı aştığında
süreci sonlandırır. Büyük çıktı üreten komutlarda spawn kullanılır ve çıktı akışla
işlenir.
Çıkış Kodu ve Sonlandıran Sinyal
Bir alt sürecin bitişi iki farklı yolla olabilir: kendi isteğiyle çıkarak veya bir
sinyalle sonlandırılarak. close olayı bu iki durumu ayrı alanlarda bildirir.
// alt-surec-sinyal.mjs import { spawn } from 'node:child_process'; import { once } from 'node:events'; // Uzun surecek bir is; kendiliginden bitmesini beklemeyecegiz const cocuk = spawn('sleep', ['30']); console.log('cocuk baslatildi, ayri surec mi:', cocuk.pid !== process.pid); setTimeout(() => cocuk.kill('SIGTERM'), 100); const [kod, sinyal] = await once(cocuk, 'close'); console.log('cikis kodu:', kod, '| sonlandiran sinyal:', sinyal);
node alt-surec-sinyal.mjs
cocuk baslatildi, ayri surec mi: true cikis kodu: null | sonlandiran sinyal: SIGTERM
Sinyalle sonlandırılan bir sürecin çıkış kodu yoktur; alan null gelir. Yalnızca
koda bakan bir denetim, kod === 0 sınamasını geçemez ama kod !== 0 sınamasını da
farklı yorumlayabilir. Doğru denetim iki alanı birlikte okur: kod sıfırsa ve
sinyal yoksa başarı.
Sinyal adının kendisi Kabuk Programlama kursunda tanıtılmıştı; SIGTERM kibar bir
sonlanma isteğidir ve süreç onu yakalayıp temizlik yapabilir. Zorla sonlandıran
sinyal yakalanamaz ve temizliğe fırsat vermez; bu yüzden yalnızca kibar istek
yanıtsız kaldığında kullanılır. Sürecin kendi kapanma davranışı Süreç Yönetimi ve
Sağlamlık dersinin konusudur.
Aynı Çalışma Zamanından İkinci Süreç
Ölçüm toplayıcının pahalı işi kendi kodudur, dış bir araç değil. Bunun için fork
kullanılır: aynı çalışma zamanının yeni bir örneğini başlatır ve iki süreç arasında
bir ileti kanalı kurar.
// ozet-cocuk.mjs import { readFile } from 'node:fs/promises'; process.on('message', async ({ dosya }) => { const metin = await readFile(dosya, 'utf8'); const kovalar = new Map(); for (const satir of metin.split('\n')) { if (satir.trim() === '') continue; const { dugum, metrik, deger } = JSON.parse(satir); const anahtar = `${dugum}/${metrik}`; const kova = kovalar.get(anahtar) ?? { sayi: 0, toplam: 0 }; kova.sayi += 1; kova.toplam += deger; kovalar.set(anahtar, kova); } process.send({ pidVar: typeof process.pid === 'number', kovaSayisi: kovalar.size }); process.disconnect(); });
// ozet-ana.mjs import { fork } from 'node:child_process'; import { once } from 'node:events'; const cocuk = fork('./ozet-cocuk.mjs'); console.log('ayri surec mi:', cocuk.pid !== process.pid); cocuk.send({ dosya: 'olcumler.ndjson' }); const [ileti] = await once(cocuk, 'message'); console.log('cocuktan gelen:', ileti); const [kod] = await once(cocuk, 'exit'); console.log('cocuk cikis kodu:', kod);
node ozet-ana.mjs
ayri surec mi: true
cocuktan gelen: { pidVar: true, kovaSayisi: 6 }
cocuk cikis kodu: 0
İki süreç arasında hiçbir bellek paylaşılmaz. send ile verilen değer dizileştirilip
kanaldan geçirilir ve karşı tarafta yeniden kurulur; dolayısıyla yalnızca
dizileştirilebilir değerler taşınabilir — fonksiyon, sınıf örneği ve dosya tanıtıcısı
doğrudan geçmez. Bu kopyalama, büyük veri için ölçülebilir bir maliyettir: çocuğa
milyonlarca kayıt göndermek yerine, dosya adını gönderip okumayı ona bırakmak
tercih edilir. Yukarıdaki örnek tam olarak bunu yapıyor.
disconnect çağrısı kanalı kapatır. Kanal açık kaldığı sürece iki süreç de birbirini
ayakta tutar; çocuk işini bitirdiği hâlde çıkmıyorsa nedeni genellikle budur.
Yalıtımın bir yararı daha vardır: çocuk sürecin çökmesi ana süreci düşürmez. Ana
süreç exit olayını görür, kararı kendisi verir — yeniden başlatabilir, isteği
hatayla yanıtlayabilir veya işi kendi yapabilir.
Özet
spawn, programı ayrı süreç olarak başlatır ve üç standart akışını nesne olarak verir; büyük çıktı akışla işlenir.- Kabuk üzerinden çalıştırma, komut metnini yorumlar; metne dışarıdan gelen bir parça eklendiğinde ek komut çalıştırılabilir. Program adı ve argüman dizisi ayrı verilir.
- Çıkış kodu ile sonlandıran sinyal ayrı alanlardır; sinyalle sonlandırılan sürecin kodu yoktur ve başarı denetimi iki alanı birlikte okur.
fork, aynı çalışma zamanının ikinci örneğini başlatır ve ileti kanalı kurar; iletiler dizileştirilerek kopyalanır, bellek paylaşılmaz.- Süreç yalıtımı, çocuğun çökmesini ana sürecin kararına dönüştürür.
Sonraki Adım
Alt süreç, işi ana iş parçacığından çıkarmanın bir yoludur ama ağır bir yoludur: her süreç kendi belleğini ve kendi çalışma zamanı örneğini taşır, aralarındaki her ileti kopyalanır. Sonraki ders iki alternatifi ele alır — aynı bağlantı noktasını paylaşan süreç kümesi ve aynı süreç içinde bellek paylaşabilen iş parçacıkları — ve hangisinin hangi iş türüne uygun olduğunu belirler.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.