Ders 12 / 17
Yarış Koşulları
Aynı kontrolün eşzamanlı isteklerde kaç kez geçtiğinin ölçülmesi: tek kullanımlık bir kotada on altı eşzamanlı istek denetimsiz kurulumda on altı kez geçer, kilit ve tek yazarlı kuyruk bunu kotaya indirir, yeniden denemesiz atomik koşullu güncelleme kota üçken yalnız bir isteği geçirerek iki meşru isteği reddeder; yedi kontrol noktasının dördü korumasızdır ve sekiz eşzamanlı istekte yirmi yedi fazla geçiş üretir, tek yazarlı kuyruk bunu sıfıra indirir ve sekizinci nokta eklendiğinde dağıtık kurulumda fazla geçiş otuz dörde çıkar.
İçindekiler
Önceki dersin bütün ölçümü tek bir varsayıma dayanıyordu: adımlar birbiri ardına çalışıyor. Durum makinesi ödeme adımına geldiğinde durumu okuyor, kararı veriyor ve durumu güncelliyordu. Okuma ile yazma arasında geçen sürede başka hiçbir şeyin olmadığı varsayılıyordu.
İki istek aynı anda geldiğinde bu varsayım düşer. İkisi de durumu onaylandi okuyabilir,
ikisi de kararı geçebilir ve ikisi de yazabilir. Kural kodda yazılıdır, denetim çalışır ve
sonuç yine yanlıştır. Yarış koşulu budur: kusur denetimin varlığında değil, denetim ile
etkinin arasındaki boşluktadır.
Üç Adımın Arasına Girmek
Paylaşılan bir değeri değiştiren her istek üç adımdır: oku, denetle, yaz. Bu üçlünün arasına başka bir isteğin girmesi, ikinci isteğin birincinin henüz yazmadığı değeri okuması demektir. İki istek de eski değere bakarak karar verir ve iki karar da olumlu çıkar.
Aşağıdaki ölçüm bir modeldir: gerçek bir sunucu, gerçek bir istemci ve gerçek bir ağ yoktur. Zamanlama gerçek değildir; istekler adım adım serpiştirilerek kendi yazdığımız düzenleyicide koşar, bu yüzden çıktı her koşumda aynıdır. Bir servise eşzamanlı istek göndererek kural atlatmayı denemek yazılı yetki ve tanımlı kapsam olmadan hem yasadışı hem meslek dışıdır; ölçülen şey burada kendi modelimizin davranışıdır.
// yaris/model.mjs — eszamanli istek modeli (model): gercek bir sunucu, gercek bir istemci ve // gercek bir ag yok. Zamanlama gercek degil, adim adim serpistirmeyle uretilir; bu yuzden // cikti her kosumda aynidir. // KH17: her istek uc adimdir — paylasilan durumu oku, karar ver, yaz. Yaris kosulu bu ucun // arasina baska bir istegin girmesiyle dogar. export function* istek(ad, durum, kurulum) { const yerel = durum.kalan; // 1. oku yield "oku"; const gecti = yerel > 0; // 2. denetle yield "denetle"; if (!gecti) return { ad, sonuc: "reddedildi" }; if (kurulum === "kuyruk") return { ad, sonuc: "kuyrukta" }; if (kurulum === "atomik" && durum.kalan !== yerel) return { ad, sonuc: "carpisma" }; durum.kalan = yerel - 1; // 3. yaz return { ad, sonuc: "gecti" }; } // KH18: dort kurulum ayni istek kumesini kosar. "denetimsiz" her istegi adim adim serpistirir; // "kilit" oku-denetle-yaz ucluslunu serilestirir; "atomik" yazmadan once degeri yeniden sinar; // "kuyruk" yazmayi tek yazarli bir kuyruga birakir ve kuyruk yeniden denetler. export function kosu(eszamanli, kurulum, kota = 1) { const durum = { kalan: kota }, sonuc = []; const uretec = Array.from({ length: eszamanli }, (_, i) => istek(`i-${i + 1}`, durum, kurulum)); if (kurulum === "kilit") { for (const u of uretec) { let r; while (!(r = u.next()).done); sonuc.push(r.value); } } else { const acik = uretec.map((u) => ({ u, bitti: false })); while (acik.some((a) => !a.bitti)) for (const a of acik) { if (a.bitti) continue; const r = a.u.next(); if (r.done) { a.bitti = true; sonuc.push(r.value); } } } if (kurulum === "kuyruk") for (const s of sonuc) if (s.sonuc === "kuyrukta") s.sonuc = durum.kalan > 0 ? (durum.kalan--, "gecti") : "reddedildi"; const gecen = sonuc.filter((s) => s.sonuc === "gecti").length; return { gecen, fazla: Math.max(0, gecen - kota), kalan: durum.kalan }; } // KH19: kontrol noktasi, paylasilan bir degeri okuyup denetleyip yazan her yerdir. export const NOKTA = [ { ad: "indirim-kodu-kullan", kota: 1, kurulum: "denetimsiz" }, { ad: "iade-ode", kota: 1, kurulum: "denetimsiz" }, { ad: "kota-dus", kota: 3, kurulum: "kilit" }, { ad: "abone-devri-onay", kota: 1, kurulum: "kilit" }, { ad: "is-emri-atama", kota: 1, kurulum: "denetimsiz" }, { ad: "fatura-kilitle", kota: 1, kurulum: "atomik" }, { ad: "sayac-okuma-al", kota: 2, kurulum: "denetimsiz" }, ];
// yaris/olcum.mjs — ayni kontrol kac kez geciyor, kilit ve tek yazarli kuyruk kacini kapatiyor. import { kosu, NOKTA } from "./model.mjs"; const yaz = (g, ...s) => console.log(s.map((v, i) => (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join("")); const KURULUM = ["denetimsiz", "kilit", "atomik", "kuyruk"]; const A = [-11, 13, 8, 9, 9]; console.log("1. tek kullanimlik indirim kodu (kota 1): kac istek kontrolden gecti"); yaz(A, "eszamanli", ...KURULUM); for (const n of [1, 2, 4, 8, 16]) yaz(A, n, ...KURULUM.map((k) => kosu(n, k).gecen)); const B = [-11, 13, 8, 9, 9]; console.log("\n2. ayni olcum kota 3 ile (gecmesi gereken en cok 3 istek)"); yaz(B, "eszamanli", ...KURULUM); for (const n of [1, 2, 4, 8, 16]) yaz(B, n, ...KURULUM.map((k) => kosu(n, k, 3).gecen)); // KH20: fazla gecen istek, kotanin ustunde gecen istek sayisidir; olculen sey budur. const ESZAMANLI = 8; const C = [-22, 7, 13, 9, 9, 11]; console.log(`\n3. ${NOKTA.length} kontrol noktasi, her birinde ${ESZAMANLI} eszamanli istek`); yaz(C, "kontrol noktasi", "kota", "kurulum", "gecen", "fazla", "kuyrukta"); let fazlaToplam = 0, kuyrukToplam = 0; for (const n of NOKTA) { const s = kosu(ESZAMANLI, n.kurulum, n.kota), k = kosu(ESZAMANLI, "kuyruk", n.kota); fazlaToplam += s.fazla; kuyrukToplam += k.fazla; yaz(C, n.ad, n.kota, n.kurulum, s.gecen, s.fazla, k.fazla); } yaz(C, "toplam", "", "", "", fazlaToplam, kuyrukToplam); // 4. kapsama: yeni kontrol noktasi eklendiginde const YENI = { ad: "odeme-plani-olustur", kota: 1, kurulum: "denetimsiz" }; const genis = [...NOKTA, YENI]; const fazla = (kume, merkezi) => kume.reduce((t, n) => t + kosu(ESZAMANLI, merkezi ? "kuyruk" : n.kurulum, n.kota).fazla, 0); const D = [-36, 8, 12, 14, 13]; console.log("\n4. kapsama: korumasiz kontrol noktasi ve fazla gecen istek"); yaz(D, "kume", "nokta", "korumasiz", "fazla gecen", "disarida %"); for (const [ad, k, merkezi] of [ ["bugunku dagitik durum", NOKTA, false], ["tek yazarli kuyruk butun noktalarda", NOKTA, true], ["yeni nokta eklendi, dagitik", genis, false], ["yeni nokta eklendi, merkezi kalip", genis, true], ]) { const acik = merkezi ? 0 : k.filter((n) => n.kurulum === "denetimsiz").length; yaz(D, ad, k.length, acik, fazla(k, merkezi), ((acik / k.length) * 100).toFixed(1)); }
1. tek kullanimlik indirim kodu (kota 1): kac istek kontrolden gecti eszamanli denetimsiz kilit atomik kuyruk 1 1 1 1 1 2 2 1 1 1 4 4 1 1 1 8 8 1 1 1 16 16 1 1 1 2. ayni olcum kota 3 ile (gecmesi gereken en cok 3 istek) eszamanli denetimsiz kilit atomik kuyruk 1 1 1 1 1 2 2 2 1 2 4 4 3 1 3 8 8 3 1 3 16 16 3 1 3 3. 7 kontrol noktasi, her birinde 8 eszamanli istek kontrol noktasi kota kurulum gecen fazla kuyrukta indirim-kodu-kullan 1 denetimsiz 8 7 0 iade-ode 1 denetimsiz 8 7 0 kota-dus 3 kilit 3 0 0 abone-devri-onay 1 kilit 1 0 0 is-emri-atama 1 denetimsiz 8 7 0 fatura-kilitle 1 atomik 1 0 0 sayac-okuma-al 2 denetimsiz 8 6 0 toplam 27 0 4. kapsama: korumasiz kontrol noktasi ve fazla gecen istek kume nokta korumasiz fazla gecen disarida % bugunku dagitik durum 7 4 27 57.1 tek yazarli kuyruk butun noktalarda 7 0 0 0.0 yeni nokta eklendi, dagitik 8 5 34 62.5 yeni nokta eklendi, merkezi kalip 8 0 0 0.0
Sayılar ölçüm sınıfındadır; girdileri KH17–KH20’dir.
Kontrol Eşzamanlılık Kadar Geçiyor
Birinci tablo tek kullanımlık bir indirim kodunu ölçüyor: kota 1, yani en çok bir istek geçmelidir. Denetimsiz kurulumda geçen istek sayısı eşzamanlı istek sayısına eşit — 2 istek 2 kez, 16 istek 16 kez geçiyor. Kontrol her istekte gerçekten çalıştı ve her istekte olumlu sonuç verdi, çünkü hepsi yazmadan önce okudu.
Bu, denetimin etkisiz olduğu anlamına gelmez; ölçeklenmediği anlamına gelir. Tek istekle koşulan bir sınama bu kusuru hiçbir zaman göremez: birinci satırda dört kurulum da 1 veriyor. Kusur ancak eşzamanlılık bir değişken olduğunda görünür.
Kilit ve tek yazarlı kuyruk her eşzamanlılık düzeyinde 1 veriyor. İkisi aynı sonuca iki farklı yoldan varıyor: kilit oku–denetle–yaz üçlüsünü serileştirir, kuyruk okumayı serbest bırakıp yalnız yazmayı tek yazara toplar ve yazma anında yeniden denetler. Kuyruğun ayırt edici özelliği, yavaş okumaların kilit tutmamasıdır.
Kilidin de bir bedeli vardır ve o bedel bu ölçümde görünmez. Kilit, kritik bölümü
serileştirdiği için eşzamanlılık arttıkça bekleyen istek sayısını artırır; kritik bölümün
içinde bir okuma yavaşsa bütün istekler o okumayı sırayla bekler. Tablodaki gecen sütunu
bu bekleme süresini ölçmez, yalnız doğruluğu ölçer. İki sütunun ayrı tutulması gerekir:
doğruluk sütununda kilit ile kuyruk aynı sonucu verir, gecikme sütununda vermez.
Savunmanın Bedeli: Çarpışma
İkinci tablo aynı ölçümü kota 3 ile yineliyor ve bir savunmanın bedelini açığa çıkarıyor. Denetimsiz kurulum yine eşzamanlılık kadar geçiriyor; kilit ve kuyruk 3’te duruyor. Atomik koşullu güncelleme ise 1 veriyor.
Sebep modeldeki gerçekleştirimdir: yazma anında değer değişmişse istek çarpışma sayılıp reddediliyor ve yeniden denenmiyor. Kota 3 iken üç isteğin geçmesi gerekirken yalnız biri geçiyor; iki meşru istek kaybediliyor. Koşullu güncelleme fazla geçişi engelliyor ama yeniden deneme olmadan kullanıldığında meşru işin bir bölümünü de durduruyor.
Bu, bu müfredatın kuralının bir örneğidir: bedeli yazılmayan savunma ölçülmemiş sayılır. Koşullu güncellemenin doğru biçimi çarpışan isteği belirli bir sınıra kadar yeniden denemektir; sınır da bir karardır ve yükün altında kaç isteğin düşeceğini belirler.
İkinci tablonun kota 1 satırlarıyla kota 3 satırları arasındaki fark da bir uyarıdır. Kota 1 iken atomik kurulum doğru sonucu verir ve kusur görünmez; kusur ancak kota 1’in üstüne çıktığında ortaya çıkar. Bir savunmanın tek bir yapılandırmada doğru davranması, bütün yapılandırmalarda doğru davrandığı anlamına gelmiyor. Ölçüm kümesinin en az iki kota değeri içermesi gerekir.
Yedi Noktada Yirmi Yedi Fazla Geçiş
Üçüncü tablo ölçümü tek bir koddan kod tabanına taşır. Yedi kontrol noktası var; her biri paylaşılan bir değeri okuyup denetleyip yazıyor. Dördü korumasız, ikisi kilitli, biri atomik.
Her noktada sekiz eşzamanlı istek koşuyor ve fazla geçen istek sayısı yazılıyor: kotanın üstünde geçen istek. Korumasız dört nokta sırasıyla 7, 7, 7 ve 6 fazla geçiş üretiyor; toplam 27. Korumalı üç nokta 0 üretiyor.
Sayılar arasındaki fark kotadan gelir. sayac-okuma-al noktasının kotası 2 olduğu için sekiz
istekten altısı fazladır; kotası 1 olan noktalarda yedisi. Fazla geçiş sayısı bu yüzden nokta
sayısıyla değil, kota ile eşzamanlılığın farkıyla ölçülür. “Dört noktamız korumasız”
cümlesi 27 sayısını vermez.
Son sütun aynı noktaların tek yazarlı kuyrukla koşulduğunda ne verdiğini gösteriyor: yedi noktanın yedisinde 0. Kapatma tek bir yerdedir; her nokta için ayrı bir kilit tasarlanmadı.
Korumalı üç noktanın hangileri olduğu da anlamlıdır. kota-dus ve abone-devri-onay
kilitlidir, çünkü ikisi de para ya da abonelik durumu değiştiren, üzerinde uzun düşünülmüş
noktalardır. Korumasız dördü ise bir sayaç düşüren, bir kayıt işaretleyen ya da bir atama
yapan noktalardır; hiçbiri “kritik” görünmez. Yarış koşullarının bulunduğu yer, kritik
sayılan nokta değil, paylaşılan bir değeri okuyup yazan noktadır. Envanterin ölçütü de
bu olmalıdır: kritiklik değil, paylaşılan yazma.
Kapsama ve Sekizinci Nokta
Dördüncü tablo kapsamayı yazar. Bugünkü dağıtık durumda yedi noktanın dördü korumasız — dışarıda kalan oran %57,1 — ve toplam fazla geçiş 27. Bütün yazmalar tek yazarlı kuyruktan geçtiğinde korumasız nokta 0, fazla geçiş 0.
Sekizinci bir nokta — odeme-plani-olustur — dağıtık kurulumda korumasız eklenirse fazla
geçiş 27’den 34‘e, dışarıda kalan oran %62,5’e çıkar. Merkezî kalıpta aynı nokta hiçbir
fazla geçiş üretmez; yazma yolu zaten kuyruktan geçer.
Fark burada da kalıbın gücünde değil, yazmanın tek bir yoldan yapılmasındadır. Dağıtık kurulumda her yeni kontrol noktası için “bu değeri kim daha yazıyor” sorusunun yeniden sorulması gerekir; kuyrukta soru bir kez sorulmuştur. Eşzamanlılık, kod incelemesinde en zor görülen kusur sınıfıdır: kod tek başına okunduğunda doğrudur ve yalnız iki kopyası aynı anda koştuğunda yanlıştır.
Bu yüzden ölçümün kendisi bir sınama biçimidir. Yukarıdaki düzenleyici gerçek zamanlama kullanmadığı için sonucu her koşumda aynıdır; aynı serpiştirme her kontrol noktasına uygulanabilir ve fazla geçiş sayısı bir sayı olarak kaydedilebilir. Gerçek zamanlamaya bırakılan bir sınama ise bazen geçer bazen kalır ve geçtiğinde hiçbir şey kanıtlamaz. Bir yarış koşulunun varlığı, kendi yazdığımız serpiştirmede gösterilebiliyorsa gösterilmiş sayılır; gösterilemiyorsa yokluğu değil, o serpiştirmede görünmediği bilinir.
Özet
- Yarış koşulu denetimin yokluğunda değil, denetim ile etkinin arasındaki boşlukta doğar; oku–denetle–yaz üçlüsünün arasına giren ikinci istek eski değeri okur.
- Tek kullanımlık bir kotada denetimsiz kurulum eşzamanlılık kadar geçiş üretir: 16 eşzamanlı istek 16 kez geçer. Tek istekle koşulan sınama bu kusuru hiçbir zaman göremez.
- Kilit ve tek yazarlı kuyruk her düzeyde kotayı korur; kuyruğun ayırt edici yanı okumaların kilit tutmamasıdır.
- Yeniden denemesiz atomik koşullu güncelleme kota 3 iken yalnız 1 isteği geçirir; fazla geçişi kapatırken iki meşru isteği de reddeder. Bedeli yazılmayan savunma ölçülmemiştir.
- Yedi kontrol noktasının dördü korumasız; sekiz eşzamanlı istekte toplam 27 fazla geçiş üretiyorlar. Fazla geçiş sayısı nokta sayısıyla değil, kota ile eşzamanlılığın farkıyla ölçülür.
- Tek yazarlı kuyruk fazla geçişi 0’a indirir; sekizinci korumasız nokta eklendiğinde dağıtık kurulumda fazla geçiş 34’e çıkarken merkezî kalıpta 0 kalır.
Sonraki Adım
Bu konu beş derste tek bir soruyu ölçtü: kimin ne yapabildiği kodun kaç noktasında denetleniyor, kaç nokta dışarıda kalıyor. Kimlik doğrulama noktalarında sızdırılan bilgi sayıldı, oturum akışlarında yenilenmeyen kimlik ve yaşamaya devam eden oturum sayıldı, yetki matrisinde nesne düzeyinde denetimi olmayan uç ve yanlış geçen istek sayıldı, akış sırasında kural dışı yol sayıldı ve bu derste eşzamanlı isteklerin ürettiği fazla geçiş sayıldı. Beş ölçümün beşinde de aynı biçim çıktı: dağıtık savunma her kümede dışarıda nokta bıraktı, merkezî kalıp aynı kümede sıfır verdi ve her kümeye yeni bir kalem eklendiğinde dağıtık tarafın dışarıda bıraktığı arttı, kalıbınki değişmedi.
Bütün bu denetimlerin dayandığı bir şey ise hiç ele alınmadı. Kimlik doğrulama bir değeri karşılaştırdı, oturum kimliği bir yerden üretildi, yetki ölçütü bir imzaya ya da bir kayda güvendi, kuyruk bir kimlikle çalıştı. Bu değerlerin nasıl üretildiği, ne kadar tahmin edilebilir oldukları ve kod tabanının neresinde durdukları sorulmadı — anahtarlar ve sırlar hâlâ kodun içinde bir yerde duruyor. Sonraki konu, Kriptografi ve Sırlar, bu soruyu aynı kapsama ölçüsüyle ele alır: kaç sır var, kaçı kod tabanının dışında, kaçı depo geçmişinde kalmış.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.