İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat