Ders 13 / 24
Web İşçileri
Hesabın ana iş parçacığı dışına taşınması; paylaşılmayan bellek modeli, ileti geçişi, yapılandırılmış kopyalamanın kapsamı ve maliyeti, aktarılabilir nesneler, işçi türleri ve ayrımın ne zaman kazanç sağladığı.
İçindekiler
Önceki dersteki yeteneklerin çoğu ağır veri üretir: kameradan gelen kareler, sensör okumaları, seçilen bir ölçüm dosyasının binlerce satırı. Bunların işlenmesi ana iş parçacığında yapıldığında kare bütçesi tek bir çağrıda tükenir ve arayüz yanıt vermeyi keser.
İşi parçalara bölmek gecikmeyi dağıtır ama toplam süreyi kısaltmaz; hesap yine aynı iş parçacığında yapılır. İkinci çözüm, hesabı oradan tümüyle çıkarmaktır. Tarayıcı bunun için ayrı iş parçacıklarında çalışan bir yürütme ortamı sunar.
Paylaşılmayan Bellek
Çalışan (worker), ayrı bir iş parçacığında yürüyen ve sayfanın bir kopyası olmayan bir yürütme ortamıdır. Kendi genel nesnesi, kendi öbeği ve kendi olay döngüsü vardır. Bundan üç sonuç çıkar.
Belge ağacına erişemez. Düğüm seçmek, biçem okumak, öge eklemek çalışanın işi değildir. Arayüzü güncelleyen tek yer ana iş parçacığıdır; çalışan yalnızca veri üretir.
Değişken paylaşmaz. Ana iş parçacığındaki bir nesne çalışanda görünmez. Aralarındaki tek bağ ileti geçişidir (message passing). Bu, Eşzamansız JavaScript ve Çalışma Zamanı kursundaki tek iş parçacıklı modelin bozulmadığı anlamına gelir: iki taraf da kendi içinde tek iş parçacıklıdır, aralarında ortak durum yoktur ve klasik yarış koşulları oluşmaz.
Ayrı bir kaynak tüketir. Her çalışan kendi belleğini ve kendi çalışma zamanı yapısını taşır; sayısı sınırsız artırılmaz.
Bir Çalışanı Çalıştırmak
Aşağıdaki örnek node:worker_threads ile çalışır ve gerçek bir iş parçacığı açar.
Tarayıcıdaki karşılığı aynı modeli izler: çalışan bir betik adresiyle kurulur, iki taraf
ileti gönderip alır. Adlandırma ayrıntıları ayrılır — tarayıcı tarafında başlangıç verisi
ayrı bir iletiyle gönderilir — ancak akış aynıdır.
// isci.mjs — hesabin ayri bir is parcacigina tasinmasi (node:worker_threads) import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads"; // Ayni dosya hem ana is parcaciginda hem calisanda calisir; dal isMainThread ile ayrilir. function ozet(olcumler) { let toplam = 0; let enBuyuk = -Infinity; let esikUstu = 0; for (const d of olcumler) { toplam += d; if (d > enBuyuk) enBuyuk = d; if (d > 5) esikUstu += 1; } return { sayi: olcumler.length, ortalama: toplam / olcumler.length, enBuyuk, esikUstu }; } if (isMainThread) { // Belirlenimci veri: her calistirmada ayni dizi uretilir. const olcumler = Array.from({ length: 200000 }, (_, i) => Math.sin(i / 7) * 10); const calisan = new Worker(new URL(import.meta.url), { workerData: olcumler }); calisan.on("message", (sonuc) => { console.log("sonuc :", { sayi: sonuc.sayi, ortalama: Number(sonuc.ortalama.toFixed(6)), enBuyuk: Number(sonuc.enBuyuk.toFixed(6)), esikUstu: sonuc.esikUstu, }); }); calisan.on("error", (hata) => console.log("hata :", hata.message)); calisan.on("exit", (kod) => console.log("cikis :", kod)); console.log("ana is parcacigi calisani kurdu ve beklemeden devam etti"); } else { parentPort.postMessage(ozet(workerData)); }
ana is parcacigi calisani kurdu ve beklemeden devam etti
sonuc : { sayi: 200000, ortalama: 0.000399, enBuyuk: 10, esikUstu: 66672 }
cikis : 0
Çıktının sırası modelin özetidir. Ana iş parçacığı çalışanı kurar ve beklemeden devam eder; sonuç geldiğinde geri çağırım işler. İki yüz bin öge üzerindeki döngü boyunca ana iş parçacığı boştur ve olayları işlemeyi sürdürür.
Sonuçtaki en büyük değer, örnek verinin üretiminden gelir: sinüs işlevinin en büyük değeri bire yaklaşır, on ile çarpıldığında ondur. Eşik üstü sayı da veriden türer; veri belirlenimci olduğu için her çalıştırmada aynı çıkar. Çıkış kodunun sıfır olması çalışanın hatasız kapandığını bildirir.
İleti Geçişinin Bedeli
İleti geçişi ücretsiz değildir. Gönderilen değer paylaşılmaz, kopyalanır. Kopyalama kuralı JSON’unkinden geniştir ve ayrı bir adı vardır.
// kopya.mjs — yapilandirilmis kopyalama ve aktarilabilir nesne const kayit = { kod: "T-01", an: new Date("2024-02-11T06:00:00Z"), etiketler: new Map([["gece", true]]), ornek: new Uint8Array([3, 1, 4]), }; const kopya = structuredClone(kayit); console.log("tarih korundu :", kopya.an instanceof Date, "| eslem korundu:", kopya.etiketler instanceof Map); console.log("dizi korundu :", kopya.ornek instanceof Uint8Array, "| ayni nesne mi:", kopya.ornek === kayit.ornek); // JSON'un yapamadigi: dongusel basvuru const dugum = { ad: "istasyon" }; dugum.kendisi = dugum; const dugumKopya = structuredClone(dugum); console.log("dongu korundu :", dugumKopya.kendisi === dugumKopya); // Kopyalanamayan degerler hata uretir. for (const [ad, deger] of [["islev", { f: () => 1 }], ["simge", { s: Symbol("x") }]]) { try { structuredClone(deger); console.log(ad.padEnd(14), ": kopyalandi"); } catch (hata) { console.log(ad.padEnd(14), ":", hata.name); } } // Aktarim: arabellek kopyalanmaz, sahipligi devredilir. const arabellek = new ArrayBuffer(1024); const aktarilan = structuredClone(arabellek, { transfer: [arabellek] }); console.log("aktarim sonrasi kaynak:", arabellek.byteLength, "| hedef:", aktarilan.byteLength);
tarih korundu : true | eslem korundu: true dizi korundu : true | ayni nesne mi: false dongu korundu : true islev : DataCloneError simge : DataCloneError aktarim sonrasi kaynak: 0 | hedef: 1024
Yapılandırılmış kopyalama (structured clone), Depolama API’leri dersinde görülen JSON kayıplarını yaşamaz: tarih tarih kalır, eşlem eşlem kalır, döngüsel başvuru korunur. Buna karşılık kopya gerçekten kopyadır; iki taraftaki nesneler ayrı nesnelerdir ve birinde yapılan değişiklik diğerine yansımaz.
Kopyalanamayan değerler vardır. İşlev ve simge kopyalanamaz ve hata üretir. Aynı kural düğümler için de geçerlidir; bir ögeyi çalışana göndermek mümkün değildir. Bu, belge ağacına erişememenin ileti tarafındaki karşılığıdır.
Aktarılabilir nesneler (transferable objects) kopyalama maliyetinden kaçmayı sağlar. Aktarım listesine konan bir arabellek kopyalanmaz; sahipliği karşı tarafa geçer ve gönderende kullanılamaz hâle gelir — çıktıda kaynağın uzunluğu sıfırdır. Büyük ikili veri bu yolla neredeyse bedelsiz taşınır, ancak gönderen tarafın onu bir daha kullanmayacağı kesin olmalıdır.
Bir de gerçekten paylaşılan bellek vardır: iki iş parçacığının aynı arabelleği görmesi. Bu yol, sayfanın diğer kaynaklardan yalıtılmış olmasını gerektiren koşullara bağlıdır ve paylaşılan belleğe erişim, işlemleri bölünmez kılan bir düzenekle sıraya konmalıdır. Yarış koşullarını geri getirdiği için en son başvurulan yoldur.
Türler ve Yaşam Süresi
Çalışan iş parçacıklarının üç türü vardır. Adanmış çalışan (dedicated worker) onu kuran belgeye aittir; belge kapandığında sonlanır. Paylaşılan çalışan (shared worker) aynı kaynağın birden çok belgesine hizmet eder ve bağlantı noktaları üzerinden konuşur; sekmeler arasında ortak bir hesabı bir kez yapmaya yarar. Servis çalışanı (service worker) ise ayrı bir amaca hizmet eder — ağ isteklerine aracılık eder ve belge kapalıyken de çalışabilir; Servis Çalışanları dersinin konusudur.
Çalışan açıkça sonlandırılabilir. Sonlandırma kesindir: çalışan iş yarıda kalır. Kullanıcı başka bir görünüme geçtiğinde sürmekte olan bir hesabın sonlandırılması, önceki derslerdeki iptal kuralının bu bağlamdaki karşılığıdır. Sonlandırılmayan çalışan bellekte kalır ve kaynak tüketmeyi sürdürür.
Hata yönetimi de ayrıdır. Çalışanın içinde yakalanmayan bir hata ana iş parçacığına bir olayla bildirilir; çalışanın çökmesi sayfayı çökertmez. Bu yalıtım bir kazançtır: güvenilmez bir hesap ana iş parçacığından ayrı tutulur.
Ne Zaman Değer
Ayrımın bir kurulum maliyeti vardır: iş parçacığının açılması, betiğin yüklenmesi, verinin kopyalanması, sonucun geri kopyalanması. Küçük bir iş için bu maliyet, kazancından büyüktür.
Ölçüt şudur: iş saf hesap mı, uzun mu, belge ağacından bağımsız mı. Üç koşul birden sağlanıyorsa çalışan kazandırır — büyük bir ölçüm dosyasının çözümlenmesi, görüntü işleme, sıkıştırma, arama dizini kurma. Koşullardan biri düşerse önceki dersteki bölme yöntemi daha uygundur.
Veri aktarımının kendisi de ölçüte girer. Kopyalanacak veri hesaptan pahalıysa kazanç kaybolur; bu durumda ham veriyi çalışana bir kez gönderip ardından yalnızca küçük iletilerle çalışmak ya da aktarılabilir nesne kullanmak gerekir.
Özet
- Çalışan iş parçacığı kendi belleğine ve olay döngüsüne sahiptir; belge ağacına erişemez ve ana iş parçacığıyla yalnızca ileti geçişi üzerinden konuşur.
- Ana iş parçacığı çalışanı kurduktan sonra beklemez; sonuç geldiğinde geri çağırım işler.
- İleti geçişinde değer yapılandırılmış kopyalamayla taşınır; JSON’un kaybettiği tipler ve döngüsel başvurular korunur, işlev ve simge gibi değerler hata üretir.
- Aktarılabilir nesnelerde kopyalama yapılmaz, sahiplik devredilir ve gönderen tarafta kullanılamaz hâle gelir.
- Adanmış çalışan belgeye, paylaşılan çalışan aynı kaynağın belgelerine hizmet eder; servis çalışanı ağ aracılığı için ayrı bir tür oluşturur.
- Çalışan, iş saf hesapsa, uzunsa ve belge ağacından bağımsızsa kazandırır; kurulum ve kopyalama maliyeti bu koşullar sağlanmadığında kazancı yutar.
Sonraki Adım
Bu konu boyunca sayfaya davranış eklendi: olaylar devredildi, varsayılan davranışlara müdahale edildi, durum depolandı ve adrese yazıldı, ölçümler gözlendi, hesap ana iş parçacığından çıkarıldı. Her yetenek ayrı ayrı kullanılabilir durumda; ancak hepsi hâlâ sayfanın genel kodunda dağınık duruyor. Ölçüm satırının kendi yapısı, kendi biçemi ve kendi davranışı bir arada değil.
Yükü dağıtmanın bir sonraki adımı, arayüz parçasının kendini toparlamasıdır: bir ögenin kendi etiketini, kendi yaşam döngüsünü ve kendi davranışını tarayıcının tanıdığı biçimde tanımlaması. Sonraki konu bu paketlemeyle başlar; ilk dersi olan Özel Elemanlar, kendi adına sahip bir ögenin tarayıcıya nasıl tanıtıldığını ve ağaca girip çıkarken hangi kancaların çağrıldığını ele alır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.