İçeriğe geç
academia.sh

Ders 14 / 14

Yarış Koşulu Saptayıcı

Saptayıcı dilin kendi dağıtımının parçası ve program içinden çağrılıyor: kurgulanmış bir kaynak geçici bir dizine yazılıp saptayıcı seçeneğiyle koşturuluyor, yalnız çıkış kodu okunuyor. Üç kurulumdan korumasız paylaşım buluyor, kilitli paylaşım ve kanal üzerinden aktarım bulamıyor. Sınırlayıcı ölçüm — kursun son ve en önemli sınırı — aynı korumasız kod hiç koşmayan bir dalın içine konduğunda saptayıcının onu da bulamadığını gösteriyor: saptayıcı yalnız koşan yolu görür.

İçindekiler

Üç ders boyunca paylaşılan durum kilitle, bekleme grubuyla, bir kez çalıştırmayla ve atomik işlemlerle korundu — ama her ölçüm korumanın var olduğunu baştan biliyordu, çünkü kaynağı biz yazdık. Gerçek bir kaynak tabanında korumanın unutulup unutulmadığını, kaynağı okuyarak değil, kim söylüyor? Bu son ders bu soruyu dilin kendi dağıtımıyla gelen yarış koşulu saptayıcısına soruyor.

Bu kursun ikinci sert kısıtı üç ders boyunca yarış koşulunun hiç koşturulmamasını zorunlu kıldı — modellenerek ya da ayrık bellekle dışlanarak. Bu son derste bu kısıt tersine dönüyor: saptayıcının kendisini ölçebilmek için ona gerçekten yakalayabileceği kurgulanmış bir yarış vermek gerekiyor. Ölçülen şey yine yarışın sonucu değil — sayacın hangi değere ulaştığı hiçbir yerde okunmuyor — yalnız saptayıcının verdiği tek bitlik yanıt: buldu mu, bulamadı mı.

Saptayıcı Programın İçinden Nasıl Çağrılır

Saptayıcı, Go Komut Ailesi dersinde ölçülen derleyici, biçimlendirici ve inceleyici gibi dilin kendi dağıtımının bir üyesi ve aynı yordamla çağrılıyor: kurgulanmış kaynak geçici bir modüle yazılıyor, komut o dizinde çalıştırılıyor, sonuç okunuyor. Fark, okunan sonucun ne olduğunda — burada ne bir çıktı metni ne bir biçim farkı okunuyor, yalnız çıkış kodu. Saptayıcı bir yarış bulduğunda çıkış kodu sıfırdan farklı oluyor; bulamadığında program normal biçimde sıfırla kapanıyor.

Saptayıcı, derleyiciden ve inceleyiciden farklı olarak kaynağı yalnız okuyup bir karar vermiyor; kaynağı çalıştırıyor ve çalışırken her bellek okuma ile yazmasını izliyor. Bu izleme çağrının kendisine bir seçenek olarak veriliyor — komut normalde çalıştırdığı programı hiç izlemeden koşturuyor, saptayıcı seçeneği verildiğinde her goroutine’in her bellek erişimine bir gözlemci ekleyerek koşturuyor. Bu gözlem bir bedelle geliyor: aynı program saptayıcıyla koşarken normalden daha yavaş çalışıyor ve daha çok bellek kullanıyor, çünkü her erişimin kim tarafından, ne zaman yapıldığı ayrıca kaydediliyor. Bu ders bu bedeli hiç ölçmüyor — kursun süre yasağı burada da geçerli — yalnız saptayıcının verdiği ikili yanıtı okuyor.

// araclar/araclar.go — yaris kosulu saptayicisini program icinden cagiran ortak yordam
package araclar

import (
	"os"
	"os/exec"
	"path/filepath"
)

// SaptayiciBulduMu, verilen kaynagi gecici bir modulde saptayiciyla kosturur; saptayici
// bir yaris buldugunda cikis kodu sifirdan farkli olur. Saptayicinin kendi ciktisi hicbir
// yere yazilmiyor.
func SaptayiciBulduMu(kaynak string) bool {
	dizin, _ := os.MkdirTemp("", "sinama")
	defer os.RemoveAll(dizin)
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module sinama\n\ngo 1.24\n"), 0o644)
	os.WriteFile(filepath.Join(dizin, "main.go"), []byte(kaynak), 0o644)

	cmd := exec.Command("go", "run", "-race", ".")
	cmd.Dir = dizin
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() != nil
}

SaptayiciBulduMunun kendi çıktıyı hiç okumaması bilinçli: cmd.Stdout ile cmd.Stderr hiç atanmıyor, bu yüzden saptayıcının yazdığı her şey boş aygıta gidiyor. Bu, önceki derslerdeki panik metninin basılmamasıyla aynı gerekçeye dayanıyor — saptayıcının çıktısı gerçek bellek adresleri, gerçek goroutine numaraları ve bir yığıt izi taşıyor; ölçülen tek şey cmd.Run()ın döndürdüğü hata, yani komutun çıkış kodu sıfır mıydı değil miydi. Alt süreç GOTOOLCHAIN=local ile çağrılıyor, tıpkı Go Komut Ailesi dersindeki gibi — ağdan bir araç zinciri inmesin diye.

Üç Kurulum, Saptayıcının Verdiği Yanıt

// main.go — uc kurulumda saptayicinin verdigi yanit, ve hic kosmayan bir yoldaki yarisin gorunmezligi
package main

import (
	"fmt"

	"ders/araclar"
)

func main() {
	korumasiz := `package main

func main() {
	var sayac int
	bitti := make(chan struct{}, 2)
	go func() { sayac++; bitti <- struct{}{} }()
	go func() { sayac++; bitti <- struct{}{} }()
	<-bitti
	<-bitti
}
`

	kilitli := `package main

import "sync"

func main() {
	var sayac int
	var kilit sync.Mutex
	bitti := make(chan struct{}, 2)
	go func() {
		kilit.Lock()
		sayac++
		kilit.Unlock()
		bitti <- struct{}{}
	}()
	go func() {
		kilit.Lock()
		sayac++
		kilit.Unlock()
		bitti <- struct{}{}
	}()
	<-bitti
	<-bitti
}
`

	kanalli := `package main

func main() {
	kanal := make(chan int)
	go func() { kanal <- 1 }()
	<-kanal
}
`

	kosmayanYol := `package main

func main() {
	var sayac int
	bitti := make(chan struct{}, 2)
	kosmayanDal := false
	if kosmayanDal {
		go func() { sayac++; bitti <- struct{}{} }()
		go func() { sayac++; bitti <- struct{}{} }()
		<-bitti
		<-bitti
	}
	_ = sayac
}
`

	fmt.Println("-- uc kurulumda saptayicinin verdigi yanit --")
	fmt.Printf("korumasiz paylasim      : buldu=%v\n", araclar.SaptayiciBulduMu(korumasiz))
	fmt.Printf("kilitli paylasim        : buldu=%v\n", araclar.SaptayiciBulduMu(kilitli))
	fmt.Printf("kanal uzerinden aktarim : buldu=%v\n", araclar.SaptayiciBulduMu(kanalli))

	fmt.Println()
	fmt.Println("-- sinirlayici olcum: hic kosmayan yoldaki yaris gorunmuyor --")
	fmt.Printf("kosmayan daldaki yaris   : buldu=%v\n", araclar.SaptayiciBulduMu(kosmayanYol))
}
-- uc kurulumda saptayicinin verdigi yanit --
korumasiz paylasim      : buldu=true
kilitli paylasim        : buldu=false
kanal uzerinden aktarim : buldu=false

-- sinirlayici olcum: hic kosmayan yoldaki yaris gorunmuyor --
kosmayan daldaki yaris   : buldu=false

PD12. Üç kaynak birbirinden yalnız korumanın biçimiyle ayrılıyor. korumasiz, iki goroutine’in aynı sayaca hiç korumasız yazması — birinci dersin KorumaliSayacının kilitsiz bırakılmış hâli, ama bu kez gerçekten paylaşılan tek bir belleğe gerçekten aynı anda yazılıyor. kilitli aynı yazmayı bir sync.Mutex ile sarıyor. kanalli ise hiçbir paylaşılan belleğe dokunmuyor, değeri bir kanaldan geçiriyor. Saptayıcı yalnız birincide true veriyor — kilitli paylaşım ile kanal üzerinden aktarım, iki ayrı yöntemle, ikisi de saptayıcının gözünde temiz.

kanallinin temiz çıkması bu kursun ilk dersinden beri kurulan bir örüntüyü doğruluyor: bir kanaldan geçen değer kopyalanıyor ve gönderen taraf değeri gönderdikten sonra ona bir daha dokunmuyor, bu yüzden iki goroutine’in aynı belleğe aynı anda erişmesi gibi bir durum hiç kurulmuyor. kilitlinin temiz çıkması da birinci dersin doğrudan bir doğrulaması: Lock/Unlock çifti arasına alınan tek bir satır, saptayıcının da onayladığı gibi, iki goroutine’in aynı anda çalışmasını gerçekten engelliyor. Üç ders boyunca yazıyla anlatılan şey burada saptayıcının kendi gözlemiyle doğrulanıyor.

Bu üç sonuç, üç ders boyunca modellenerek ya da ayrık bellekle dışlanan yarışın nihayet gerçekten var olduğu tek yer: korumasiz kurulumu, ilk dersin kilitsiz sayacından farklı olarak, otuz ayrı goroutine yerine yalnız iki goroutine kullanıyor ve her ikisi de aynı sayac++ işlemine aynı anda giriyor — okuma, artırma ve yazmanın üç ayrı adımı arada kesilebiliyor. Saptayıcı bu kesilmeyi çalışma zamanında her bellek erişimini izleyerek yakalıyor; kilitli ve kanallı kurulumlarda böyle bir kesilme hiç yaşanmadığı için saptayıcı hiçbir şey bulmuyor, kaynağın “temiz” olduğunu değil, bu koşumda hiçbir ihlal görmediğini bildiriyor.

Sınırlayıcı Ölçüm: Saptayıcı Koşan Yolu Görür

PD13. kosmayanYol, korumasiz ile birebir aynı yarışı taşıyor — aynı iki goroutine, aynı korumasız sayac++. Tek fark, bu goroutine’lerin kosmayanDal adlı, hiçbir zaman true olmayan bir koşulun içine yerleştirilmiş olması. Saptayıcı bu kaynağı da buldu=false diye yanıtlıyor — kaynakta bir yarış var, ama saptayıcı onu bulamıyor, çünkü o goroutine’ler bu koşumda hiç başlamadı. Saptayıcı kaynağı okuyup bir tehlike analizi yapmıyor; yalnız çalışan programı gözlemliyor ve gözlemlemediği bir bellek erişimini hiçbir biçimde göremiyor.

Bu, kursun kapanış sınırı: saptayıcı bir kanıt değil, bir gözlem aracı. Bir kaynağın saptayıcıdan buldu=false ile geçmesi, o kaynakta yarış olmadığını değil, o koşumda çalışan yolların hiçbirinde yarış görülmediğini söylüyor. kosmayanYol bu farkı en açık biçimde gösteriyor — kaynak birebir aynı tehlikeyi taşırken yanıt tersine dönüyor, çünkü tehlikeli dal bu çalıştırmada hiç seçilmedi. Bir kod tabanında nadiren giren bir dal, bir hata işleme yolu, ya da bir yapılandırmaya bağlı bir dal, saptayıcının hiç görmediği bir yarışı taşıyabiliyor — testler o dalı koşturmadığı sürece.

Bu sınır saptayıcıyı gereksiz kılmıyor, yalnız neyi garanti ettiğini daraltıyor. Saptayıcı buldu=true dediğinde bu kesin bir kanıt — kurgulanmış korumasiz kaynağında olduğu gibi, gerçekten bir ihlal gözlendi. Ama buldu=false bir garanti değil, yalnız o koşumun bir raporu. Bu, testin genel doğasıyla aynı sınıra giriyor: bir testin geçmesi kodun doğru olduğunu kanıtlamıyor, yalnız test edilen yolların o koşumda beklenen davranışı verdiğini gösteriyor — saptayıcı bu sınırı eşzamanlılık için bir kez daha kuruyor, çalışmayan bir dal ne kadar iyi yazılmış olursa olsun hiç sınanmamış demektir.

Kurs Kapanışı

On dört ders boyunca bu kursun tek sorusu şuydu: bir goroutine nasıl duracak? Ortak tanımın üç iddiası bu soruya üç yanıt verdi. Birincisi: bir goroutine başlatmak, onun biteceği anlamına gelmiyor — on iki goroutine’in yalnız ikisi kendi durdu, geri kalanı başka bir goroutine’in attığı bir adıma bağlıydı. İkincisi: durmamak hata vermiyor — üç sızıntının üçü de sessizdi, program normal çıkışla kapandı. Üçüncüsü, bu kursa özgü olanı: durduran şey kanalın kendisi değil, birinin yaptığı bir iş — kilit, bekleme grubu, bir kez çalıştırma, hepsi aynı bağımlılığı farklı bir düzenekle kuruyor.

Bir sınırlayıcı okuma ve bir kör nokta bu üç iddiayı tamamlıyor. Sınırlayıcı okuma: “kendi durdu” da bir sözleşme, çünkü gövdesi biten bir goroutine bunu kimseye kendiliğinden bildirmiyor — bildirmesi için yine bir kanal ya da bir sayaç gerekiyor. Kör nokta: sızıntının sayısı hiçbir derste ölçülmedi, yalnız varlığı boolean olarak ölçüldü, çünkü çalışma zamanının kendi goroutine sayısı taban bir değere göre karşılaştırılabiliyordu, gerçek sayı değil.

Bu son ders dördüncü bir gözlemi ekliyor: korumanın var olup olmadığını kaynağı okuyarak söylemek güç, ama saptayıcı da bunu tam olarak söylemiyor — yalnız koştuğu yolda bir ihlal görüp görmediğini söylüyor. Kurs aşırı bir genelleme yapmadan kapanıyor: bu on dört ders Go’nun eşzamanlılık modelinin tamamını kapsamıyor, yalnız bir goroutine’in nasıl durduğu ve bir paylaşımın nasıl korunduğu sorularına, dört düzenek ve bir saptama aracı üzerinden, ölçülebilir yanıtlar verdi.

Ders Ölçülen durma biçimi Sınıfı (kendi · beklediği geldi · iptal · hiç durmadı) Sınırlayıcı ölçüm
Goroutine On iki goroutine, dört durma biçimi (2/5/2/3 dağılımı) kendi · beklediği geldi · iptal · hiç durmadı (hepsi) “Kendi durdu” da bir sözleşme — bildirim gerekir
Kanallar Tamponsuz kanalda gönderim-alım buluşması beklediği geldi Kanal bir kuyruk değil, değer kopyalanır
Tamponlu ve Tamponsuz Kanallar Kapasiteye göre engellemenin ertelenmesi beklediği geldi Tampon eşzamanlamayı erteler, kaldırmaz
select Deyimi Birden çok kanal arasından hazır olanın seçimi beklediği geldi Aynı anda hazır dallarda seçim rastgele, koşturulmadan modellendi
Kanal Kapatma ve Aralık Kapanan kanaldan alımın sıfır değer ve false vermesi beklediği geldi Kapalı kanala gönderim ve ikinci kapatma panikler, yalnız panik=true/false yazılır
İşçi Havuzları İş kanalı kapanınca işçilerin durması beklediği geldi İşçi sayısını artırmak işi bölmez; kapanmayan kanalda sızıntı
Yayma ve Toplama Yayılan işin toplama kanalında birleşmesi, toplayıcının durma sebebi beklediği geldi Toplama kanalını yalnız biri kapatabilir; kapatılmazsa toplayıcı hiç durmaz
Boru Hatları Her aşamanın kendi kanalını açıp kapatarak zinciri sürdürmesi beklediği geldi Tüketici erken vazgeçtiğinde önündeki aşamalar hiç durmaz
Bağlam Paketi Tek bir iptal, ctx.Done() üzerinde bekleyen goroutine’lerin hepsine aynı anda ulaşıyor iptal Bağlam kimseyi zorla durdurmaz, yalnız haber verir; Done()u denetlemeyen goroutine işini sürdürür
Zaman Aşımı ve Son Tarihler Zaman aşımı bir select dal seçimidir; süre değil, seçilen dal ölçülür iptal (hazır olan dal) Bekleyeni kurtarır ama beklenen işi durdurmaz; iş goroutine’i koşmaya devam eder ve sızıntı boolean’ı bunu gösterir
Dışlama Kilitleri Kilit bırakılınca bekleyenin devam etmesi beklediği geldi Kilit yalnız kapsamı korur, kapsam dışı korumasız kalır
Bekleme Grupları Sayaç sıfıra inince Waitin dönmesi beklediği geldi Sıra garanti edilmez, yalnız hepsinin bittiği garanti edilir
Eşzamanlılık İlkelleri Bir kez çalıştırmanın gövdeyi bir kez koşturması beklediği geldi Atomik işlem tek değişkeni korur, ikisi birden koşturulmadan modellendi
Yarış Koşulu Saptayıcı Saptayıcının üç kurulumdaki yanıtı kendi (korumasız/kilitli) · beklediği geldi (kanallı) Saptayıcı yalnız koşan yolu görür, koşmayan yoldaki yarışı bulamaz

Özet

  • Saptayıcı dilin kendi dağıtımının bir üyesi ve program içinden çağrılıyor; kurgulanmış bir yarış geçici bir modülde saptayıcı seçeneğiyle koşturulup yalnız çıkış kodu okundu.
  • Üç kurulumdan yalnız korumasız paylaşım buldu=true verdi; kilitli paylaşım ile kanal üzerinden aktarım saptayıcının gözünde temiz çıktı — üç ders boyunca yazıyla anlatılan ayrımı doğrudan doğruladı.
  • Aynı korumasız kaynak hiç koşmayan bir dalın içine konduğunda saptayıcı onu bulamadı: saptayıcı bir kanıt değil, yalnız o koşumda çalışan yolların bir gözlemi.
  • Ortak tanımın üç iddiası — kendi durmak azınlıkta, durmamak sessiz, durduran şey birinin yaptığı bir iş — on dört ders boyunca dört ayrı düzenekle doğrulandı.
  • Kurs on dört dersin ölçtüğü yanıtları tek bir tabloda topladı; her satır kendi kaynak dersinden okundu.

İ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