Ders 12 / 20
Küme ve İşçi İş Parçacıkları
Aynı bağlantı noktasını paylaşan süreç kümesi, işçi iş parçacığının bellek modeli, iki yaklaşımın hangi iş türüne uyduğu ve ölçülen engelleme farkı.
İçindekiler
Önceki ders alt süreç başlatmayı ele aldı ve maliyetini belirtti: her süreç kendi belleğini ve kendi çalışma zamanı örneğini taşır, aralarındaki her ileti kopyalanır.
Bu ders iki özel biçimi inceler. İlki, aynı bağlantı noktasını paylaşan süreç kümesidir ve amacı çok çekirdekli bir makinede birden çok isteği gerçekten aynı anda işlemektir. İkincisi, aynı süreç içinde çalışan ve bellek paylaşabilen iş parçacıklarıdır; amacı pahalı bir hesabı ana iş parçacığından çıkarmaktır.
Neden Tek Süreç Yetmiyor
Olay döngüsü dersinde ölçülen sınır burada bağlayıcı hâle gelir. Tek süreç, tek bir çekirdekte çalışan tek bir iş parçacığında istekleri işler. Girdi/çıktı ağırlıklı bir hizmette bu yeterlidir: süreç zamanının çoğunu yoklama aşamasında bekleyerek geçirir ve bir çekirdek onlarca eşzamanlı bağlantıya yeter.
İşin bir bölümü işlemciye bağlı olduğunda durum değişir. Makinede kaç bağımsız yürütme birimi olduğu sorulabilir:
// cekirdek.mjs import os from 'node:os'; console.log('kullanilabilir paralellik:', os.availableParallelism());
node cekirdek.mjs
kullanilabilir paralellik: 12
Bu sayı makineye ve sürece tanınan kısıtlara göre değişir; örnek bir çalıştırmada alınmıştır. Tek süreç, bu birimlerin yalnızca birini kullanır.
Süreç Kümesi
node:cluster modülü, bir birincil süreç ile birkaç işçi süreç kurar. İşçiler
birincil sürecin çatalladığı ayrı süreçlerdir; aynı dinleyen soketi paylaşırlar.
// kume.mjs import cluster from 'node:cluster'; import { createServer } from 'node:http'; const ISCI_SAYISI = 2; if (cluster.isPrimary) { console.log(`birincil surec ${process.pid}, ${ISCI_SAYISI} isci baslatiyor`); for (let i = 0; i < ISCI_SAYISI; i += 1) cluster.fork(); cluster.on('exit', (isci, kod, sinyal) => { console.log(`isci ${isci.process.pid} bitti (kod ${kod}, sinyal ${sinyal}); yerine yenisi`); cluster.fork(); }); } else { createServer((istek, yanit) => { yanit.writeHead(200, { 'content-type': 'text/plain' }); yanit.end(`istegi karsilayan isci: ${process.pid}\n`); }).listen(8791, '127.0.0.1', () => { console.log(`isci ${process.pid} dinliyor`); }); }
Sunucuyu bir uçbirimde çalıştırıp başka bir uçbirimden dört istek gönderdiğimizde:
node kume.mjs
for i in 1 2 3 4; do curl -s http://127.0.0.1:8791/; done
birincil surec 96467, 2 isci baslatiyor isci 96470 dinliyor isci 96469 dinliyor istegi karsilayan isci: 96470 istegi karsilayan isci: 96469 istegi karsilayan isci: 96470 istegi karsilayan isci: 96469
Süreç kimlikleri her çalıştırmada değişir; iki işçinin hangi sırayla dinlemeye başladığı da değişir. Sabit olan, isteklerin iki farklı süreç tarafından karşılanmasıdır — dağılımın örnekteki gibi düzgün sırayla olması güvence altında değildir.
Aynı bağlantı noktasını iki sürecin dinleyebilmesi, İnternet Nasıl Çalışır kursunda tanıtılan dinleyen soket kavramına dayanır. Birincil süreç soketi bir kez açar ve tanıtıcıyı işçilere devreder; gelen bağlantı, tanıtıcıyı paylaşan süreçlerden birine düşer. Bağlantı noktası sayısı bir tanedir; onu paylaşan süreç sayısı birden çoktur.
exit olayına bağlanan geri çağrı, düşen işçinin yerine yenisini koyar. Bu, sağlamlık
açısından kümenin asıl kazancıdır: bir işçide oluşan ölümcül hata hizmeti bütünüyle
durdurmaz. Yeniden başlatmanın sonsuz döngüye girmemesi için hızının sınırlanması
gerekir; işçi her açılışta hemen çöküyorsa yenisini açmak arızayı gizler.
Kümenin temel kısıtı, işçiler arasında bellek paylaşılmamasıdır. Ölçüm
toplayıcının kova haritası her işçide ayrı ayrı birikir ve /ozet yanıtı isteği
karşılayan işçiye göre değişir. Durum tutan bir hizmeti kümelemek, durumu süreç
dışına — paylaşılan bir depoya — taşımayı gerektirir.
İşçi İş Parçacıkları
node:worker_threads modülü, aynı süreç içinde ayrı bir iş parçacığı başlatır. Her
iş parçacığının kendi olay döngüsü ve kendi öbeği vardır; bellek varsayılan olarak
paylaşılmaz ama paylaşılabilir bir arabellek üzerinden açıkça paylaştırılabilir.
Pahalı bir hesabı iş parçacığına verelim:
// agir-is.mjs import { parentPort, workerData } from 'node:worker_threads'; // Olcum degerlerinin ustunde bilerek pahali bir hesap function agirToplam(yineleme) { let toplam = 0; for (let i = 0; i < yineleme; i += 1) toplam += Math.sqrt(i) % 1; return toplam; } parentPort.postMessage(Number(agirToplam(workerData.yineleme).toFixed(3)));
// isci-ana.mjs import { Worker } from 'node:worker_threads'; import { once } from 'node:events'; const YINELEME = 3e8; setTimeout(() => console.log('2 ana is parcacigi bos: zamanlayici calisti'), 0); console.log('1 isci baslatildi'); const isci = new Worker('./agir-is.mjs', { workerData: { yineleme: YINELEME } }); const [sonuc] = await once(isci, 'message'); console.log('3 isciden gelen sonuc:', sonuc); await once(isci, 'exit');
node isci-ana.mjs
1 isci baslatildi 2 ana is parcacigi bos: zamanlayici calisti 3 isciden gelen sonuc: 149989897.293
Aynı hesap ana iş parçacığında yapıldığında sıra değişir:
// isci-yok.mjs const YINELEME = 3e8; function agirToplam(yineleme) { let toplam = 0; for (let i = 0; i < yineleme; i += 1) toplam += Math.sqrt(i) % 1; return toplam; } setTimeout(() => console.log('3 zamanlayici ancak simdi calisti'), 0); console.log('1 hesap basliyor'); console.log('2 sonuc:', Number(agirToplam(YINELEME).toFixed(3)));
node isci-yok.mjs
1 hesap basliyor 2 sonuc: 149989897.293 3 zamanlayici ancak simdi calisti
İki çıktının farkı ölçünün kendisidir. İlkinde zamanlayıcı hesap sürerken çalıştı; ana iş parçacığı boştu ve o sırada gelen bir isteği işleyebilirdi. İkincisinde zamanlayıcı hesabın bitmesini bekledi — bir sunucuda bu, bütün eşzamanlı isteklerin beklemesi demektir.
Sonuç değeri iki çalıştırmada aynı çıkıyor; hesap belirlenimcidir ve iş parçacığına taşınması sonucu değiştirmez.
Hangisi Ne Zaman
İki yaklaşım birbirinin yerine geçmez.
| Ölçüt | Süreç kümesi | İşçi iş parçacığı |
|---|---|---|
| Yalıtım | tam; biri çökerse diğeri sürer | zayıf; ölümcül hata süreci düşürebilir |
| Bellek | ayrı; her süreç kendi örneğini taşır | ortak süreç, ayrı öbek; paylaşılabilir arabellek olanaklı |
| Başlatma maliyeti | yüksek | düşük |
| İleti maliyeti | dizileştirme + süreçler arası kopya | yapısal kopya; büyük veri aktarılabilir |
| Uygun iş | eşzamanlı istek sayısını çoğaltmak | tek bir pahalı hesabı ana yoldan çıkarmak |
Ölçüm toplayıcı için kural şudur: istek işleme kapasitesini artırmak gerekiyorsa küme, tek bir isteğin içindeki pahalı hesabı ana iş parçacığından çıkarmak gerekiyorsa işçi iş parçacığı kullanılır. İkisi birlikte de kullanılabilir — her küme işçisi kendi iş parçacığı havuzunu tutabilir.
İş parçacığı başlatmanın da bir bedeli vardır: yeni bir yürütme ortamı kurulur ve modüller yeniden değerlendirilir. İstek başına bir iş parçacığı açmak, hesabın kendisinden pahalıya gelebilir. Yaygın çözüm, önceden açılmış birkaç iş parçacığını havuzda tutup işleri onlara dağıtmaktır — dosya sistemi çağrılarının kullandığı havuzun aynı mantığı.
Paylaşılan bellek gerektiğinde SharedArrayBuffer üzerinden bir arabellek iki iş
parçacığına da verilebilir. Bu durumda yarış koşulları ortaya çıkar ve erişim
Atomics işlemleriyle düzenlenir; tek iş parçacıklı modelin kaldırdığı bütün
eşzamanlılık sorunları bu kapıdan geri girer. Paylaşımı ancak ölçüm sonucu gösterdiği
zaman kullanın.
Özet
- Tek süreç tek çekirdek kullanır; girdi/çıktı ağırlıklı işte bu yeterlidir, işlemciye bağlı işte değildir.
- Küme, aynı dinleyen soketi paylaşan süreçler kurar; bağlantı noktası tektir, onu paylaşan süreç sayısı birden çoktur.
- Küme işçileri bellek paylaşmaz; durum tutan bir hizmet kümelenmeden önce durumunu süreç dışına taşımalıdır.
- İşçi iş parçacığı aynı süreçte ayrı bir olay döngüsü çalıştırır; pahalı hesap oraya taşındığında ana iş parçacığı istek almaya devam eder.
- Seçim ekseni yalıtım ve paylaşımdır: kapasite çoğaltmak için küme, tek isteğin içindeki hesabı çıkarmak için iş parçacığı.
Sonraki Adım
Yerleşik modüller konusu tamamlandı: dosya, yol, akış, arabellek, olay, HTTP, alt süreç ve çok çekirdek kullanımı. Bundan sonrası bu parçaları çalışır bir uygulamaya dönüştürmekle ilgilidir. İlk adım, ölçüm toplayıcıya bir komut satırı yüzü kazandırmaktır: argümanların ayrıştırılması, boru hattından okuma ve kullanım metninin doğru akışa yazılması.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.