---
title: Eşzamanlılık
source: 'https://academia.sh/tr/kurslar/go-eszamanlilik'
language: tr
updated: '2026-08-23T18:26:29+00:00'
license: 'CC BY-SA 4.0'
lessons: 14
outcomes:
    - 'Goroutine ve kanallarla iletişim temelli eşzamanlılık kurar'
    - 'Bağlam ile iptal ve zaman aşımı yayar'
    - 'Yarış koşullarını araçla saptar'
    - "Bir goroutine'in nasıl duracağını ve durmadığında bunun nerede görüneceğini ölçer"
---

# Eşzamanlılık

Bir goroutine başlatmak tek satırdır; asıl soru onun nasıl duracağıdır ve bunu kaynağa bakarak göremezsiniz. Kurs kanallardan bağlam iptaline, işçi havuzlarından dışlama kilitlerine kadar her düzeneği tek bir soruyla ölçer: bu goroutine kendi mi duracak, beklediği geldiği için mi, iptal edildiği için mi, yoksa hiç durmayacak mı?

## Goroutine ve Kanallar

Hafif bir eşzamanlı yürütme biriminin başlatılmasıyla durmasının ayrı iki iş olması, kanalın değer aktarırken aynı anda eşzamanlama sağlaması, tamponun engelleme davranışını nerede değiştirip nerede değiştirmediği, birden çok kanal üzerinde beklemenin dal seçimi, ve kapatmanın bir sonlanma sinyali olarak taşıdığı bilgi.

### Goroutine

Source: https://academia.sh/tr/kurslar/go-eszamanlilik/goroutine

On iki goroutine'in yalnız 2'si kendi durdu; 5'i başka bir goroutine'in yaptığı bir işi bekleyip geldiği için, 2'si bir bağlamın iptal edilmesiyle, 3'ü hiç durmadan: kursun bağlayıcı dağılımı bu derste üretiliyor. Bir goroutine başlatmak tek satırdır ve o satır ne zaman duracağını hiç söylemez — durma kararı çoğunlukla goroutine'in kendi gövdesinde değil, başka birinin yaptığı bir işte duruyor. Sınırlayıcı ölçüm: kendi durdu sınıfı da bir sözleşmedir, çünkü gövdesi biten bir goroutine bunu kimseye kendiliğinden bildirmiyor.

Arayüzler, Hatalar ve Genellikler kursu aynı işin arayüzle mi genellikle mi yazıldığına göre
bir bağın nerede kurulduğunu ölçerek kapandı: on altı bağın sekizi bildirimde ya da kullanım
yerinde değil, çalışma zamanında değerin **içinde** taşınıyordu. Bu ders aynı soruyu zamana
taşıyor. `go` sözcüğü bir fonksiyon çağrısının önüne konduğunda yeni bir eşzamanlı yürütme
birimi başlar ve çağıran satır hiç beklemeden devam eder — bedeli yok gibi görünen tek
satırlık bir karar. Ama o satır bir şeyi hiç söylemez: başlattığı goroutine'in **ne zaman ve
nasıl duracağını**.

Kaynağa bakan biri `go` sözcüğünü görür, durma koşulunu göremez. Durma kararı çoğu zaman
goroutine'in kendi gövdesinde bile yazılı değildir; başka bir goroutine'in yaptığı bir işe —
bir kanala gönderilen değer, bırakılan bir kilit, iptal edilen bir bağlam — bağlıdır. Bu
kursun tek sorusu budur: **bu goroutine nasıl duracak?**

#### Başlatmakla Durmak Ayrı İki İştir

Bir goroutine başlatmak, aynı fonksiyonu doğrudan çağırmaktan tek bir sözcük farklıdır:
`f()` çağıran goroutine'de çalışır ve dönene kadar çağıranı bloklar; `go f()` yeni bir
goroutine'de çalışır ve çağıran satır hemen bir sonraki satıra geçer. Bu fark, `go`
sözcüğünün bütün sözüdür. Başlatılan goroutine'in ne zaman biteceği, bitip bitmeyeceği,
bitişinin çağırana nasıl ulaşacağı — bunların hiçbiri `go` sözcüğünün taahhüdünde değildir.

Derleyici bu boşluğu hiç sormaz: bir goroutine'in çağırdığı fonksiyonun ne zaman döneceği,
dönüp dönmeyeceği, `go` sözcüğünün derlenmesi için hiçbir koşul değildir. Bir goroutine
sonsuza kadar çalışabilir, hiç çalışmadan takılabilir, ya da bir mikrosaniyede biter —
derleyici üçünü de aynı tek satırla kabul eder. Bu, kursun M09/K01 dersinde görülen
"reddedildi" sınıfından tamamen farklı bir durum: orada derleyici yazılmayan bir bağı
kabul etmiyordu, burada yazılmayan bir bağı hiç sorgulamıyor bile.

Bu boşluk dört farklı biçimde doluyor ve doldurma işini goroutine'in kendisi değil, çoğu
zaman **başka biri** yapıyor:

- goroutine kendi gövdesini bitirip kendiliğinden duruyor (**kendi**);
- bir kanal, kilit ya da bekleme grubu üzerinde bekliyor ve başka bir goroutine'in attığı bir
  adım onu serbest bırakıyor (**beklediği geldi**);
- beklediği şey hiç gelmiyor ama bir bağlam iptal edildiği için duruyor (**iptal**);
- ya da hiçbiri olmuyor ve goroutine **hiç durmuyor**.

Bu dört sınıf bu dersin ve bütün kursun ölçü eksenidir. Go'nun eşzamanlı yürütme birimine
verdiği ad **`goroutine`**'dir ve bu ad çevrilmeden kullanılır; goroutine işletim
sisteminin zamanladığı bir iş parçacığıyla aynı şey değildir, dilin kendi çalışma zamanı
binlerce goroutine'i az sayıda gerçek iş parçacığı üzerinde kendi zamanlayıcısıyla
yürütür. Bu ayrımın kendisi bu dersin konusu değil; iki düzenek burada karşılaştırılmıyor,
yalnızca goroutine'in kendi başına bir kavram olduğu bir kez kayda geçiriliyor.

#### Durma Sebebini Saydıran Bir Çerçeve

Dört sınıfı ölçülebilir kılmanın yolu, her goroutine'in kendi durma sebebini bir kanala
yazmasını istemek ve raporları toplayıp sıralı basmaktır — goroutine'ler sıra vermez, bu
yüzden sıra ölçümün kendisinde kurulur. `Defter` tipi bunu yapıyor: `bildir` her raporu bir
kanala gönderiyor, `topla` tam olarak beklenen sayıda raporu okuyup ada göre sıralıyor.

```go
// dur.go — bir goroutine nasil duruyor: kendi mi, bekledigi geldigi icin mi, iptal edildigi icin mi, hic mi
package main

import (
	"fmt"
	"runtime"
	"sort"
)

var kendi, geldi, iptal, hic int

type rapor struct{ ad, sebep string }

// Defter raporlari toplar ve sirali basar; goroutine'ler sira vermez.
type Defter struct {
	kanal chan rapor
	taban int
}

func yeniDefter(kapasite int) *Defter {
	return &Defter{kanal: make(chan rapor, kapasite), taban: runtime.NumGoroutine()}
}

func (d *Defter) bildir(ad, sebep string) { d.kanal <- rapor{ad, sebep} }

func (d *Defter) topla(beklenen int) []rapor {
	r := make([]rapor, 0, beklenen)
	for i := 0; i < beklenen; i++ {
		r = append(r, <-d.kanal)
	}
	sort.Slice(r, func(i, j int) bool { return r[i].ad < r[j].ad })
	return r
}

func gozle(ad, sebep string) {
	switch sebep {
	case "kendi":
		kendi++
	case "geldi":
		geldi++
	case "iptal":
		iptal++
	default:
		hic++
	}
	fmt.Printf("%-38s %s\n", ad, sebep)
}

func (d *Defter) sizintiVarMi() bool { return runtime.NumGoroutine() > d.taban }

func ozet() {
	fmt.Printf("durma: %d kendi, %d bekledigi geldi, %d iptal, %d hic durmadi\n",
		kendi, geldi, iptal, hic)
}
```

**GK1.** — `bildir` yalnız bir kanala yazıyor; hiçbir goroutine kendi başına ekrana yazmıyor;
bütün `fmt.Printf` çağrıları `gozle` içinde ve yalnız ana goroutine tarafından çalıştırılıyor. Bu
ayrım rastgele değil: birden çok goroutine aynı anda ekrana yazsaydı, satırların hangi sırayla
çıkacağı da bir sıra vermezdi ve ölçüm kendi kendini bozardı. `taban` alanı `sizintiVarMi`nin neye
göre karşılaştırma yapacağını, ölçüm başlamadan önceki goroutine sayısını tutuyor.

Sıranın toplanıp sıralanması bir zevk meselesi değil, bu kursun bütün derslerinde geçerli
bir zorunluluktur: goroutine'lerin hangi sırayla bittiği çalışma zamanının zamanlama
kararına bağlıdır ve bu karar koşumdan koşuma değişebilir. Aynı sonucu her koşumda almanın
tek yolu, sırayı ya bir kanal/kilit/bekleme grubuyla zorlamak ya da sonuçları toplayıp
kendi kriterimize göre sıralamaktır; burada seçilen kriter `ad` alanının alfabetik sırası.
Sabit bir bekleme süresi koyup "muhtemelen bu kadar sürede biter" varsaymak bu iki yoldan
hiçbirine girmez — ortam yüküne göre bazen yeter, bazen yetmez ve bu derste hiç
kullanılmıyor.

#### On İki Goroutine, Dört Durma Biçimi

Ölçüm aynı pakette bu çerçeveyi kullanıyor ve on iki goroutine'i dört sınıfa dağıtıyor.
İlk ikisi kendi gövdesini bitirip duruyor. Sonraki beşi beş ayrı düzenekle bekliyor —
tamponsuz bir kanaldan alım, kapanmış bir kanalda `range`, bir kilidin bırakılması, bir
bekleme grubunun sıfırlanması, birden çok kanal üzerinde `select` — ve beşi de başka bir
goroutine'in (ya da ana goroutine'in) attığı bir adımla serbest kalıyor. İki tanesi bir
bağlamın iptaliyle duruyor. Son üçü hiç durmuyor: alıcısı olmayan bir kanala gönderim
yapmaya çalışıyor, kapanmayan bir kanalda `range` yapıyor, hiç `Done` çağrılmayan bir
bekleme grubunu bekliyor.

```go
// olcum.go — on iki goroutine, dort durma bicimi
package main

import (
	"context"
	"fmt"
	"sync"
)

func main() {
	d := yeniDefter(32)

	go func() { d.bildir("01 govdesi biten goroutine", "kendi") }()
	go func() {
		t := 0
		for i := 0; i < 3; i++ {
			t += i
		}
		_ = t
		d.bildir("02 dongusu biten goroutine", "kendi")
	}()

	kanal := make(chan int)
	go func() {
		<-kanal
		d.bildir("03 tamponsuz alim", "geldi")
	}()
	kanal <- 1

	kapanan := make(chan int, 2)
	kapanan <- 7
	close(kapanan)
	go func() {
		for range kapanan {
		}
		d.bildir("04 kapanan kanalda aralik", "geldi")
	}()

	var kilit sync.Mutex
	kilit.Lock()
	go func() {
		kilit.Lock()
		kilit.Unlock()
		d.bildir("05 kilidi bekleyen", "geldi")
	}()
	kilit.Unlock()

	var grup sync.WaitGroup
	grup.Add(1)
	go func() {
		grup.Wait()
		d.bildir("06 bekleme grubunu bekleyen", "geldi")
	}()
	grup.Done()

	secim := make(chan int, 1)
	digeri := make(chan int)
	secim <- 5
	go func() {
		select {
		case <-secim:
			d.bildir("07 select ilk hazir daldan", "geldi")
		case <-digeri:
			d.bildir("07 select ikinci daldan", "geldi")
		}
	}()

	ctx, durdur := context.WithCancel(context.Background())
	go func() {
		<-ctx.Done()
		d.bildir("08 baglam iptali bekleyen", "iptal")
	}()
	go func() {
		for {
			select {
			case <-ctx.Done():
				d.bildir("09 dongude baglam denetleyen", "iptal")
				return
			default:
			}
		}
	}()
	durdur()

	terkedilmis := make(chan int)
	go func() {
		terkedilmis <- 1
		d.bildir("10 alicisiz gonderim", "hic")
	}()

	kapanmayan := make(chan int)
	go func() {
		for range kapanmayan {
		}
		d.bildir("11 kapanmayan kanalda aralik", "hic")
	}()

	var bitmeyen sync.WaitGroup
	bitmeyen.Add(1)
	go func() {
		bitmeyen.Wait()
		d.bildir("12 hic Done almayan bekleme", "hic")
	}()

	fmt.Println("-- goroutine nasil durdu --")
	for _, r := range d.topla(9) {
		gozle(r.ad, r.sebep)
	}
	gozle("10 alicisiz gonderim", "hic")
	gozle("11 kapanmayan kanalda aralik", "hic")
	gozle("12 hic Done almayan bekleme", "hic")

	fmt.Println("gonderim simdi gecer mi                :", denenirMi(terkedilmis))
	fmt.Println("durmayan goroutine kaldi mi            :", d.sizintiVarMi())
	ozet()
}

func denenirMi(k chan int) bool {
	select {
	case k <- 1:
		return true
	default:
		return false
	}
}
```

```
-- goroutine nasil durdu --
01 govdesi biten goroutine             kendi
02 dongusu biten goroutine             kendi
03 tamponsuz alim                      geldi
04 kapanan kanalda aralik              geldi
05 kilidi bekleyen                     geldi
06 bekleme grubunu bekleyen            geldi
07 select ilk hazir daldan             geldi
08 baglam iptali bekleyen              iptal
09 dongude baglam denetleyen           iptal
10 alicisiz gonderim                   hic
11 kapanmayan kanalda aralik           hic
12 hic Done almayan bekleme            hic
gonderim simdi gecer mi                : false
durmayan goroutine kaldi mi            : true
durma: 2 kendi, 5 bekledigi geldi, 2 iptal, 3 hic durmadi
```

**GK2.** — İlk dokuz satır `d.topla(9)` çağrısından geliyor ve `ad`e göre sıralanmış
çıkıyor; son üçü ölçümün kendisi `gozle` ile elle basıyor, çünkü bu üçü hiç rapor
göndermiyor ve `topla` onları hiç bekleyemez. Dokuz sayısı bu derste **veriden** değil
**kurgudan** geliyor — kaç goroutine'in rapor göndereceğini yazar baştan biliyor, ölçüm
bunu doğrulamıyor; bu, üçüncü bölümdeki sınırlayıcı ölçümün konusu.

On iki goroutine'in **2'si kendi durdu, 5'i beklediği geldiği için, 2'si iptal edildiği
için, 3'ü hiç durmadı.** Bu dağılım kursun geri kalanı için bağlayıcı; sonraki dersler
kendi diliminin satırlarını basıp bu sayıya tek cümleyle atıf yapacak. `GOMAXPROCS=1` dahil
her koşumda aynı çıktı üretiliyor, çünkü hiçbir sıra çizelgeleyiciye bırakılmadı — her
adım ya bir kanal, bir kilit ya da bir bekleme grubuyla zorlandı, ya da sonuçlar toplanıp
sıralandı.

#### Üç İddia

Bu tablonun okunması üç iddiaya dayanıyor. Birincisi: **bir goroutine başlatmak, onun
biteceği anlamına gelmiyor.** On ikinin yalnız ikisi kendi durdu; kalan onunun durması
başka birinin yaptığı bir işe bağlı — beşi bir adımın gelmesine, ikisi bir iptale, üçü de
hiçbir şeye, çünkü hiç durmadılar. Kaynağa bakan biri `go` sözcüğünü görür, ama bu on
satırın hangisinin ne zaman duracağını kaynaktan okuyamaz.

İkincisi: **durmamak hata vermiyor.** Üç sızıntının üçü de sessiz; program normal çıkışla
sonlanıyor, hiçbir uyarı üretilmiyor. Kanıt yalnız dolaylı: terk edilmiş kanala gönderim
**şimdi de geçmiyor** (`denenirMi` `false` döndürüyor) ve taban sayının üstünde goroutine
kalmış (`sizintiVarMi` `true` döndürüyor). Sayının kendisi hiçbir yerde basılmıyor, çünkü
`runtime.NumGoroutine` çalışma zamanının kendi goroutine'lerini de sayabiliyor; kural budur
— **sızıntının sayısı hiçbir derste basılmaz, varlığı `true`/`false` olarak yazılır.**

Üçüncüsü: **durduran şey kanal değil, birinin yaptığı iştir.** Beş "beklediği geldi" satırı
beş ayrı düzenekle kuruldu — tamponsuz alım, kapanan kanalda aralık, kilit, bekleme grubu,
`select`. Düzenek her satırda değişiyor; değişmeyen şey **bağımlılık**: her birinde başka
bir goroutine'in (kimi zaman ana goroutine'in) bir adımı olmasa bekleyen hiç durmayacaktı.
İletişim ile paylaşım arasındaki tercih ilerideki derslerin konusu; burada önemli olan,
hangi düzenek seçilirse seçilsin, durmanın **her zaman birinin bir işine** bağlı kalması.

Bu üç iddianın kod üzerindeki karşılığı satır satır izlenebilir. Üçüncü ve dördüncü
satırlar (`03`, `04`) aynı sınıfa girer ama farklı düzenekler kullanır: biri tamponsuz bir
kanaldan tek değerlik bir alım, öteki kapanmış bir kanalda tükenene kadar süren bir
`range`. Beşinci ve altıncı satırlar (`05`, `06`) da "beklediği geldi" sınıfındadır ama
beklenen şey bu kez bir değer değil, bir **durumdur** — kilidin boşalması, sayacın
sıfırlanması. Yedinci satır (`07`) ise `select` ile birden çok olası kaynaktan yalnız
hazır olanını seçiyor; bu derste iki kanaldan biri önceden dolduruldu, bu yüzden seçim
belirlenimli — iki kanal da aynı anda hazır olsaydı seçim rastgele olurdu ve bu durum
dördüncü derste ayrıca ölçülüyor.

#### Sınırlayıcı Ölçüm: "Kendi Durdu" da Bir Sözleşmedir

**GK3.** — "Kendi durdu" sınıfı en kolay göründüğü hâlde bir sözleşmesizlik taşımıyor değil.
Birinci ve ikinci goroutine gövdelerini bitirdiler ve **kendiliğinden** durdular — ama bu
bitişi kimseye bildirmediler; `d.bildir` çağrısı olmasaydı, ana goroutine'in bu ikisinin
bitip bitmediğini bilmesinin hiçbir yolu olmazdı. Ölçümün kendisi bunu kullanıyor: on iki
goroutine'in dokuzu raporunu bir kanala yazdığı için sayılabildi; ölçüm bu dokuzu **kaç
tane geleceğini önceden bilerek** bekliyor (`d.topla(9)`), gelmeyenleri değil.

Bu, bir goroutine'in bittiğini **bilmenin**, onun **bitmesinden** ayrı bir iş olduğunu gösteriyor.
Bir goroutine kendi gövdesini bitirdiğinde dil hiçbir şey yapmaz — ne bir olay yayınlar, ne bir
bayrak çevirir. Bitmiş olmak, dışarıdan görülür bir şey değildir; görülür olması için goroutine'in
kendisinin, tıpkı bu ölçümde olduğu gibi, bir kanala yazması gerekir. "Kendi durdu" ile "hiç
durmadı" arasındaki fark bu yüzden dışarıdan bakan biri için bazen görünmez: ikisi de sessizdir;
tek fark biri gerçekten bitmiş, öteki hâlâ çalışıyor olmasıdır.

Bunun pratik sonucu, "goroutine'i başlattım, gövdesi kısa, zaten bitmiştir" varsayımının
hiçbir zaman kaynaktan doğrulanamamasıdır. Birinci ve ikinci goroutine'in kaynak kodu bunu
söylemiyor; onların bittiğini bilen tek şey, bu dersin ölçümünün kurduğu kanaldır. Aynı
gövde, `d.bildir` çağrısı olmadan yazılsaydı, derleyici hiçbir şey değiştirmezdi ama ölçüm
kendisi imkânsız olurdu — dokuzuncu satırdan sonrasını beklemenin hiçbir yolu kalmazdı.

#### Özet

- Bir goroutine başlatmak (`go f()`) ile onun durması ayrı iki iştir; `go` sözcüğü durma
  koşulunu hiç söylemez.
- Bir goroutine dört biçimde durur: kendi gövdesini bitirerek (**kendi**), başka bir
  goroutine'in adımını bekleyerek (**beklediği geldi**), bir bağlamın iptaliyle
  (**iptal**), ya da hiç durmayarak (**hiç durmadı**).
- On iki goroutine'in **2'si kendi, 5'i beklediği geldi, 2'si iptal, 3'ü hiç durmadı** —
  bu dağılım kursun bağlayıcı sayısı.
- goroutine bir iş parçacığı değildir; dilin kendi çalışma zamanı goroutine'leri az sayıda
  gerçek iş parçacığı üzerinde kendi zamanlayıcısıyla yürütür.
- Sızıntı hiçbir derste sayıyla basılmaz, yalnız varlığı `true`/`false` olarak yazılır.
- "Kendi durdu" da bir sözleşmedir: bir goroutine'in bittiğini bilmek, onun bitmesinden
  ayrı bir iştir ve görünür olması için ayrıca bildirilmesi gerekir.

#### Sonraki Adım

Beş "beklediği geldi" satırının ilki bir kanaldan geldi: `kanal <- 1` yazıldı, karşı
tarafta bekleyen goroutine bunu aldı ve durdu. Ama bu satırda iki şey birden oldu — bir
değer aktarıldı ve iki goroutine bir noktada buluştu. Sıradaki ders bu iki işi ayırıyor:
bir kanal aynı anda hem bir değer taşıyor hem de göndereni ve alıcıyı eşzamanlıyor ve bu
ikisi her zaman birlikte gelmek zorunda değil.

### Kanallar

Source: https://academia.sh/tr/kurslar/go-eszamanlilik/kanallar

Bir kanal aynı satırda iki iş birden yapar: bir değeri bir goroutine'den ötekine taşır ve iki goroutine'in aynı noktada buluşmasını zorlar. Tamponsuz bir kanala gönderim, karşı tarafta bir alıcı hazır olana kadar hiç tamamlanmaz; alıcı hiç yoksa deneme kalıcı olarak geçmez. Sınırlayıcı ölçüm: kanal bir kuyruk değildir, çünkü aktarılan değer kopyalanır ve gönderen tarafın gönderimden sonraki değişikliği alıcıya hiçbir zaman ulaşmaz.

Önceki ders on iki goroutine'i dört durma biçimine ayırdı ve beş "beklediği geldi" satırının
ilkini tamponsuz bir kanaldan alımla kurdu: `kanal <- 1` yazıldı, karşı taraftaki goroutine
bunu aldı ve durdu. O satırda aslında iki ayrı iş birden oldu — bir tam sayı bir goroutine'den
ötekine geçti ve iki goroutine aynı anda aynı noktada buluştu. Bu ders bu ikisini ayırıyor:
kanal her iki işi de aynı satırda yapıyor, ama bu iki iş ayrı ayrı ölçülmeden "eşzamanlama"
sözcüğü ne söylüyor, "aktarım" sözcüğü ne söylüyor birbirine karışıyor.

Bir **kanal**, tipli bir değerin bir goroutine'den başka bir goroutine'e geçtiği tek yoldur;
`chan int`, `chan string` gibi yazılır ve `<-` işlecinin yönü gönderim ile alımı ayırır:
`kanal <- deger` bir gönderim, `deger := <-kanal` bir alımdır. Ama kanal yalnızca bir
taşıyıcı değildir. Aynı satırda ikinci bir iş daha yapar: göndereni ve alıcıyı
**eşzamanlar**. Bu ikinci iş, kanalı bir kuyruktan ya da bir posta kutusundan ayıran şeydir
ve bu dersin ölçtüğü tam olarak budur.

Bu iki işin birlikte gelmesi Go'nun eşzamanlılığa yaklaşımının merkezinde duruyor:
paylaşılan bir değişkene kilitle erişmek yerine, değeri doğrudan bir goroutine'den ötekine
göndermek. Kilit ve paylaşılan durum bu kursun Paylaşılan Durum konusunda işleniyor; bu ders
yalnız iletişimin kendi düzeneğini — kanalın ne yaptığını ve ne yapmadığını — ölçüyor.

`make(chan int)` ile oluşturulan bir kanal başlangıçta boştur ve hiçbir goroutine'e bağlı
değildir; onu kullanan tarafların sayısı sıfır, bir ya da birçok olabilir. Bu ders en basit
durumu, tek gönderen ve tek alıcıyı, ölçüyor. Birden çok goroutine'in aynı kanalı paylaştığı
durumlar — birçok goroutine'in aynı sonuç kanalına yazması, ya da birçok goroutine'in aynı
iş kanalından okuması — Eşzamanlılık Kalıpları konusunda işleniyor; kanalın kendi kuralları
burada kurulduktan sonra oraya taşınıyor.

#### Alıcısız Gönderim Hiçbir Zaman Geçmez

Tamponsuz bir kanala (`make(chan T)`, kapasitesiz) gönderim, karşı tarafta o değeri almaya
hazır bir goroutine olana kadar tamamlanmaz. Bunu süre geçirerek "muhtemelen kimse yok"
diye varsaymak yerine, `select` ile `default` dalını birlikte kullanıp **denemek**
mümkündür: gönderim o an geçebiliyorsa `select` o dalı seçer, geçemiyorsa hiç beklemeden
`default`'a düşer.

```go
// bulusma.go — tamponsuz kanalin iki isi: deger aktarmak ve alicisiz gonderimi engellemek
package main

import "fmt"

func denenirMi(k chan string, v string) bool {
	select {
	case k <- v:
		return true
	default:
		return false
	}
}

func main() {
	kanal := make(chan string)

	fmt.Println("alici hic yokken deneme gonderimi gecti mi:", denenirMi(kanal, "ilk deneme"))

	teslimAlindi := make(chan string)
	go func() {
		deger := <-kanal
		teslimAlindi <- "alici aldi: " + deger
	}()

	kanal <- "merhaba"
	fmt.Println("gonderim tamamlandi, alici degeri teslim aldi")
	fmt.Println(<-teslimAlindi)
}
```

```
alici hic yokken deneme gonderimi gecti mi: false
gonderim tamamlandi, alici degeri teslim aldi
alici aldi: merhaba
```

**GK4.** — İlk satırda `kanal` üzerinde henüz hiçbir goroutine beklemiyor; `denenirMi` bu
durumda **her koşumda** `false` döner, çünkü ortada bekleyen kimse yoktur — bu bir zamanlama
sorunu değil, tamponsuz bir kanalın tanımının doğrudan sonucudur. İkinci bölümde goroutine
başlatılıyor ve `kanal <- "merhaba"` bu kez `select` içinde değil, doğrudan yazılıyor: bu
satır alıcı gelene kadar bloklanır ve alıcı geldiğinde tamamlanır. Aradaki fark önemli:
`select`+`default` bir **anlık durumu** yokluyor, düz `kanal <- değer` ise gerektiği kadar
bekleyip **kesinlikle tamamlanana kadar** durur.

Programın geri kalanının tek bir goroutine'de (ana goroutine'de) çalıştığına dikkat
edilmeli: alıcı goroutine ekrana hiçbir şey yazmıyor, yalnız aldığı değeri `teslimAlindi`
kanalına geri gönderiyor. Bütün `fmt.Println` çağrıları ana goroutine'dedir ve aralarında
kanal üzerinden zorlanmış bir sıra vardır — bu yüzden çıktı her koşumda birebir aynıdır.

Bu ölçüm biçimi bu dersin ve kursun geri kalanının kuralıdır: bir goroutine kendi başına
ekrana yazmaz, yalnız bir kanala yazar; ekrana yazma işi hep ana goroutine'de, kanaldan
okunan değer üzerinden yapılır. İki goroutine aynı anda ekrana yazsaydı, satırların hangi
sırayla çıkacağı yine goroutine'lerin kendisi kadar belirsiz olurdu — kanal burada yalnız
değer taşımak için değil, aynı zamanda çıktının sırasını tek bir goroutine'e toplamak için
de kullanılıyor.

#### Aktarılan Değer Kopyalanır

Kanal bir değer taşırken o değerin **kopyasını** taşır, referansını değil. Bu, M09/K01 Go
Temelleri kursunda dizi ve dilim atamalarında görülen kopya/paylaşım ayrımının kanallardaki
karşılığıdır: bir kanala bir yapı değeri gönderildiğinde, göndericinin o değişkeni sonradan
değiştirmesi alıcının elindeki kopyayı etkilemez.

```go
// kopyalanir.go — kanaldan aktarilan deger kopyalanir, gonderenin sonraki degisikligi aliciya ulasmaz
package main

import "fmt"

type kayit struct {
	Ad    string
	Deger int
}

func main() {
	kanal := make(chan kayit)
	sonuc := make(chan kayit)

	go func() {
		alinan := <-kanal
		sonuc <- alinan
	}()

	k := kayit{Ad: "sayac", Deger: 1}
	kanal <- k
	k.Deger = 999
	fmt.Println("gonderenin kendi degeri simdi:", k)
	fmt.Println("alicinin teslim aldigi deger  :", <-sonuc)
}
```

```
gonderenin kendi degeri simdi: {sayac 999}
alicinin teslim aldigi deger  : {sayac 1}
```

**GK5.** — `kanal <- k` satırı tamamlandığında `k`'nin **o andaki** değeri kopyalanıp
alıcı goroutine'e geçti. Hemen ardından `k.Deger = 999` yazıldığında değişen şey yalnızca
gönderenin kendi yerel değişkeni; alıcının aldığı kopya bundan habersizdir ve `Deger`
alanı `1` olarak kalır. Bu davranış `kayit`in bir işaretçi değil bir değer tipi olmasından
geliyor — kanaldan işaretçi geçirilseydi (`chan *kayit`), kopyalanan şey işaretçinin
kendisi olur, gösterdiği veri değil; o durumda gönderenin değişikliği alıcıdan da görünür
olurdu. Bu ders yalnız değer aktarımını ölçüyor; işaretçilerin kopyalanmasıyla neyin
paylaşılıp neyin paylaşılmadığı Diziler ve Dilimler dersinde ayrı bir eksendi ve burada
tekrarlanmıyor.

Bu kopyalama kuralı hiçbir istisna taşımaz: kanalın kendi tipi ne olursa olsun — tam sayı,
dizgi, yapı, hatta bir başka kanal — gönderilen şey her zaman o değerin o andaki bit
kalıbının bir kopyasıdır. Kanal, gönderilen değere hiçbir zaman kendi tarafından bir
işaretçi eklemez; paylaşım isteniyorsa, tıpkı `kayit`in yerine `*kayit` göndermekte olduğu
gibi, bu paylaşımı isteyen tarafın kendi tipini seçmesi gerekir.

#### Kopya Yüzeyseldir: İçindeki Dilim Paylaşılır

Kopyalanan şey her zaman yalnızca **yapının kendisidir**, yapının içindeki her alan değil.
Bir yapı bir dilim alanı taşıyorsa, kanal o yapıyı kopyalarken dilimin **başlığını**
kopyalar — uzunluk, kapasite ve alt diziye işaretçi — ama alt dizinin kendisini kopyalamaz.
Bu, M09/K01 Go Temelleri kursunda diziler ve dilimlerde görülen yüzeysel kopya davranışının
kanal üzerinden aktarımdaki karşılığı.

```go
// sigkopya.go — kanaldan giden yapi kopyalanir ama icindeki dilim ayni alt diziyi paylasir
package main

import "fmt"

type sepet struct {
	Etiket string
	Ogeler []int
}

func main() {
	kanal := make(chan sepet)
	sonuc := make(chan sepet)

	go func() {
		alinan := <-kanal
		alinan.Ogeler[0] = 999
		sonuc <- alinan
	}()

	s := sepet{Etiket: "kaynak", Ogeler: []int{1, 2, 3}}
	kanal <- s
	alinanKopya := <-sonuc
	fmt.Println("alicinin kopyasi         :", alinanKopya)
	fmt.Println("gonderenin kendi degiskeni:", s)
}
```

```
alicinin kopyasi         : {kaynak [999 2 3]}
gonderenin kendi degiskeni: {kaynak [999 2 3]}
```

**GK6.** — `alinan` kanaldan gelen `sepet` değerinin kendi kopyasıdır ve `Etiket` alanı
gönderenden bağımsızdır — biri değiştirilse öteki etkilenmez, tıpkı bir önceki ölçümdeki
`kayit.Deger` gibi. Ama `Ogeler` alanı bir dilim olduğu için kopyalanan şey yalnızca üç
sözcüklük dilim başlığıdır; başlığın işaret ettiği alt dizi göndericiyle aynıdır.
`alinan.Ogeler[0] = 999` yazıldığında değişen bellek konumu, iki değişkenin de paylaştığı
o tek alt dizi konumudur — bu yüzden `s.Ogeler[0]` da `999` olarak görünüyor, oysa `s`
değişkeninin kendisine hiçbir goroutine doğrudan dokunmadı. Bu satırın ana goroutine'de
güvenle okunabilmesinin sebebi, `<-sonuc` alımının kanal işleminin kendi eşzamanlama
sözü gereği alt dizideki yazmayı da beraberinde görünür kılması; kanal yalnız `sonuc`
değerini değil, ona kadar olan bütün belleği de taşıyor.

Bu, "kanaldan aktarılan değer kopyalanır" cümlesinin sınırını gösteriyor: kopyalama
**yapının kendisiyle** biter, yapının içindeki dilim, sözlük ya da işaretçi alanlarının
gösterdiği veriyle sürmez. Bir yapıyı kanaldan bağımsız bir kopya olarak göndermek
isteyen kod, dilim alanını da ayrıca `copy` ile kopyalamak zorundadır — kanal bunu
kendiliğinden yapmaz.

#### Kanalın Yönü Tipin Bir Parçası Olabilir

Bir kanal değişkeni her iki yönde de kullanılabilir, ama bir fonksiyonun imzasında kanalın
yönü kısıtlanabilir: `chan<- int` yalnızca gönderime izin veren bir kanal tipidir, `<-chan
int` yalnızca alıma izin veren. Bu kısıtlama tipin kendisinin bir parçasıdır — aynı
`make(chan int)` ile oluşturulan çift yönlü bir kanal, yönlü bir parametreye geçirildiğinde
o fonksiyon içinde yalnız izin verilen yönde kullanılabilir.

```go
// yonlu.go — kanal yonu tipin parcasi olabilir: sadece gonderen, sadece alan
package main

import "fmt"

func uret(hedef chan<- int, adet int) {
	for i := 1; i <= adet; i++ {
		hedef <- i
	}
	close(hedef)
}

func topla(kaynak <-chan int) int {
	toplam := 0
	for v := range kaynak {
		toplam += v
	}
	return toplam
}

func main() {
	kanal := make(chan int)
	go uret(kanal, 5)
	fmt.Println("uretilen bes degerin toplami:", topla(kanal))
}
```

```
uretilen bes degerin toplami: 15
```

**GK7.** — `uret` fonksiyonuna geçirilen `kanal`, çift yönlü bir değişken olmasına rağmen
`uret`in içinde yalnızca `hedef chan<- int` olarak görünüyor; `uret`in gövdesinde `<-hedef`
yazmaya çalışmak derlenmez. Aynı şekilde `topla`nın içinde `kaynak <- 1` gibi bir gönderim
denemesi de derlenmez. Bu, kanalın hangi tarafının hangi işi yapacağını imzada okunur kılan
bir yazım kararıdır: `uret`in imzasına bakan biri, onun kanala yalnız yazacağını, hiç
okumayacağını kaynağı okumadan bilir. `uret`in kendisi `hedef`i kapatıyor da — kapatma
sorumluluğunun kimde olduğu bu dersin değil, konu kapanış dersinin ölçtüğü şey.

Yön kısıtlaması çalışma zamanında hiçbir şey değiştirmiyor; `chan<- int` ile `<-chan int`
altta yatan aynı kanal değerini paylaşıyor, farklı bir kanal türü değiller. Kazanılan şey
yalnızca derleme zamanında okunabilirlik: bir fonksiyonun imzası, o fonksiyonun kanalla
ne yapacağını taahhüt ediyor ve bu taahhüdü bozan bir çağrı derlenmiyor. `main` içindeki
`kanal` değişkeni hem `uret`e hem `topla`ya aynı çift yönlü hâliyle geçiyor; kısıtlama
yalnız o fonksiyonların **kendi içinde** geçerli oluyor.

#### İki Ölçüm Bir Arada: Taşımak ve Buluşturmak

**GK8.** — Bu iki ölçüm bir arada okunduğunda kanalın gerçek sözü ortaya çıkıyor: bir kanal
işlemi hem bir değerin **o anki hâlini** dondurup taşır, hem de göndereni ve alıcıyı aynı
noktada buluşturur. Kanalı bir kuyruğa benzetmek ilk bakışta kullanışlı bir sezgi verir —
değerler bir uçtan girer, öteki uçtan çıkar — ama bir kuyruk gönderenle alıcıyı hiç
buluşturmaz, yalnızca bir arabellek üzerinden ayırır. Tamponsuz bir kanalda böyle bir
arabellek yoktur; buluşma tamponun yerini alır. Sıradaki iki ders bu buluşmanın bir tampon
eklendiğinde nasıl **ertelendiğini** ölçüyor.

Bu iki iş her zaman aynı ağırlıkta gelmiyor. Bazı kullanımlarda önemli olan yalnız değerin
kendisi — bir hesabın sonucu, bir kaydın içeriği. Bazı kullanımlarda ise değerin ne
olduğu hiç önemli değildir, önemli olan yalnızca bir şeyin **olduğu** bilgisi; bu durumda
`chan struct{}` gibi hiçbir veri taşımayan bir tip tercih edilir ve kanal yalnız
eşzamanlama işini görür. Bu dersin `bulusma.go` örneğindeki `teslimAlindi` kanalı ilk
türden, ölçümün geri kalanında birkaç kez göreceğimiz kapatma sinyalleri ikinci türdendir.

Kanalın bir de yazılmayan durumu var: bir kanal değişkeninin sıfır değeri `nil`dir ve
`nil` bir kanal üzerindeki gönderim ile alım hiçbir zaman tamamlanmaz, sonsuza kadar bloke
kalır. Bu davranış tanımlıdır ve M09/K01 Go Temelleri kursunda sıfır değerler bağlamında
bir cümleyle geçti; bu kurs onu tekrar kurmuyor. Ama bir `nil` kanalın "sonsuza kadar
hazır olmama" özelliği, `select` içinde kullanışlı bir sonuç doğuruyor: `nil` bir kanalı
içeren bir dal, o kanal başka bir değerle değiştirilmeden asla seçilmez — bu yüzden bir
dalı geçici olarak "kapatmak" için o dalın kanalını `nil`e çevirmek yeterlidir.

```go
// nilkanal.go — nil kanal uzerindeki her islem sonsuza kadar bloke kalir, select icinde hep pas gecilir
package main

import "fmt"

func main() {
	var nilKanal chan int
	hazir := make(chan int, 1)
	hazir <- 7

	select {
	case v := <-nilKanal:
		fmt.Println("nil kanaldan alindi:", v)
	case v := <-hazir:
		fmt.Println("hazir kanaldan alindi:", v)
	default:
		fmt.Println("hicbiri hazir degil")
	}
}
```

```
hazir kanaldan alindi: 7
```

`nilKanal` hiçbir zaman `make` ile ilklendirilmedi, bu yüzden sıfır değeri olan `nil`de
kaldı. `select` üç dalı da yokladı: `nilKanal` dalı hiçbir zaman hazır olamayacağı için
elenir, `hazir` dalı doludur ve seçilir. `nilKanal` üzerinde doğrudan `<-nilKanal` yazılıp
`select` dışında çağrılsaydı program sonsuza kadar orada kalırdı; bu ders o durumu hiç
koşturmuyor, çünkü sonucu (sonsuz bloke) gözlemlenebilir bir çıktı üretmez. `select`
deyiminin kendisi bir sonraki derste ayrıntılı ölçülüyor; burada önemli olan, `nil` bir
kanalın bir dalı **etkisiz** kılmanın bir yolu olarak kullanılabildiği.

#### Özet

- Bir kanal aynı satırda iki iş yapar: bir değer taşır ve göndereni ile alıcıyı
  eşzamanlar; ikisi birlikte gelir.
- Tamponsuz bir kanalda alıcı yokken gönderim denemesi (`select`+`default`) hiçbir zaman
  geçmez; bu bir zamanlama tesadüfü değil, tanımın kendisidir.
- Doğrudan yazılan bir gönderim (`kanal <- değer`) alıcı gelene kadar bloklanır ve alıcı
  geldiğinde kesin olarak tamamlanır.
- Kanaldan aktarılan bir değer kopyalanır; gönderenin gönderimden sonraki değişikliği
  alıcıya hiçbir zaman ulaşmaz.
- `nil` bir kanal üzerindeki her işlem sonsuza kadar bloke kalır; bu davranış M09/K01'de
  kurulmuş bir tanımlılığın kanallardaki karşılığıdır.

#### Sonraki Adım

Bu derste ölçülen tamponsuz kanal, göndereni alıcı hazır olana kadar bekletiyordu — buluşma
her seferinde tam o anda kuruluyordu. Bir kanala kapasite eklendiğinde bu zorunluluk ortadan
kalkmıyor, yalnız erteleniyor. Sıradaki ders tamponun engelleme davranışını nerede
değiştirip nerede değiştirmediğini ölçüyor: kapasite dolana kadar gönderim hiç beklemeden
geçiyor, dolduğunda yine bekliyor.

Sorulacak soru şu: buluşma zorunluluğu tamamen kalkıyor mu, yoksa yalnızca ne zaman
gerçekleşeceği mi değişiyor? Bu sorunun yanıtı, tamponlu bir kanala gönderimin **hemen**
dönmesiyle, o değerin **hâlâ okunmayı beklediği** gerçeğinin birbirinden ayrılmasında
yatıyor.

### Tamponlu ve Tamponsuz Kanallar

Source: https://academia.sh/tr/kurslar/go-eszamanlilik/tamponlu-ve-tamponsuz-kanallar

Bir kanalın kapasitesi, engellemenin nerede başladığını değiştirir: kapasite dolana kadar gönderim hiç beklemeden geçer, dolduğunda tamponsuz kanaldaki gibi engeller. Üç kapasitede beş deneme gönderiminin kaçının engellenmeden geçtiği ölçülüyor. Sınırlayıcı ölçüm: tampon eşzamanlamayı kaldırmıyor, yalnız erteliyor — tamponlu bir kanala gönderim tamamlandığında gönderen, alıcının o değeri aldığını hâlâ bilmiyor.

Önceki ders tamponsuz bir kanalın iki işi birden yaptığını ölçtü: bir değer taşımak ve
göndereni ile alıcıyı aynı noktada buluşturmak. Bir kanala kapasite eklendiğinde bu iki iş
ortadan kalkmıyor — yalnız aralarındaki bağ gevşiyor. Bu ders tamponun engelleme davranışını
**nerede** değiştirdiğini ölçüyor ve neyi değiştirmediğini ayrı bir bölümde sınırlıyor.

Bir kanala kapasite vermek, kanalı kendi içinde küçük bir dizi tutan bir yapıya dönüştürür.
Bu dizinin varlığı gönderen tarafa bir tür esneklik verir: alıcı henüz hazır olmasa bile,
tampon boş yer taşıdığı sürece gönderim beklemeden ilerleyebilir. Ama bu esnekliğin bir
sınırı var ve o sınır tam olarak kapasitenin kendisi. Bu ders bu sınırı ve sınırın ne
zaman aşıldığını ölçüyor.

Kapasite, kanal oluşturulurken bir kez belirlenir ve sonradan büyütülüp küçültülemez —
`make(chan int, 3)` ile açılan bir kanal, ömrü boyunca en fazla üç değer tutabilir. Bu
sabitlik, kapasitenin bir çalışma zamanı ayarı değil, bir tasarım kararı olduğunu gösterir:
kod yazan kişi, göndericinin alıcıyı ne kadar "öne geçebileceğine" baştan karar verir. Bu
kararın doğru büyüklüğü tek bir kurala bağlanmaz — çok küçük bir kapasite göndericiyi
gereğinden sık bekletir, çok büyük bir kapasite ise alıcının ne kadar geride kaldığını
gizler; bu ders büyüklüğün nasıl seçileceğini değil, seçilen büyüklüğün ne yaptığını
ölçüyor.

#### Kapasite, Engellemenin Başladığı Noktadır

`make(chan T, n)` çağrısındaki ikinci argüman kanalın kapasitesidir; verilmezse (`make(chan
T)`) kapasite sıfırdır ve önceki dersin tamponsuz kanalı ortaya çıkar. Tamponsuz kanal ayrı
bir tip değildir — kapasitesi sıfır olan bir kanaldır ve sıfır kapasite "tampon her zaman
dolu" ile aynı sonucu verir: her gönderim, kapasitenin dolu olduğu bir kanaldaki gibi,
doğrudan bir alıcı bekler.

Bu tek fark, iki kanal kullanımının aynı sözdizimle yazılabilmesini sağlıyor: bir
fonksiyon `chan int` parametresi aldığında, çağıranın tamponlu mu tamponsuz mu bir kanal
geçirdiğini kod içinden ayırt etmek gerekmiyor — ikisi de aynı gönderim ve alım
sözdizimini kullanıyor, tek fark ne zaman bekleyeceği.

Kapasitesi `n` olan bir kanalda ilk `n` gönderim, hiçbir alıcı olmadan bile hemen
tamamlanır; kanal bu değerleri kendi içinde tutar. `n+1`. gönderim ise tamponu doldurmuş
demektir ve tıpkı tamponsuz bir kanalda olduğu gibi bir alıcı gelene kadar bekler. Bu
sınır kanalın kendi türünde değil, `make` çağrısına verilen ikinci argümanda taşınıyor;
aynı `chan int` tipi kapasite `0` ile de, kapasite `1000` ile de oluşturulabiliyor ve iki
kullanım da kaynak kodda aynı görünüyor — fark yalnızca `make` satırında.

Bunu üç ayrı kapasitede, her defasında beş deneme gönderimiyle, `select`+`default`
kullanarak ölçmek mümkündür — hiç goroutine başlatmadan, çünkü hiçbir alıcı olmaması
ölçümün bir parçası.

```go
// kapasite.go — tampon kapasitesi dolana kadar gonderim engellenmeden geciyor, dolunca deneme gecmiyor
package main

import "fmt"

func kacTaneGeciyor(kapasite, denemeSayisi int) int {
	kanal := make(chan int, kapasite)
	gecen := 0
	for i := 0; i < denemeSayisi; i++ {
		select {
		case kanal <- i:
			gecen++
		default:
		}
	}
	return gecen
}

func main() {
	for _, kapasite := range []int{0, 2, 4} {
		fmt.Printf("kapasite=%d denemeSayisi=5 gecen=%d\n", kapasite, kacTaneGeciyor(kapasite, 5))
	}
}
```

```
kapasite=0 denemeSayisi=5 gecen=0
kapasite=2 denemeSayisi=5 gecen=2
kapasite=4 denemeSayisi=5 gecen=4
```

**GK9.** — Hiçbir alıcı hiçbir zaman başlatılmadı; bu yüzden sonuç kesinlikle
belirlenimlidir ve rastgele bir zamanlamaya bağlı değildir — burada hiçbir goroutine yok,
dolayısıyla ölçülen şey zamanlama değil, kanalın kendi kapasite kuralı. Kapasite `0` iken hiçbir
gönderim geçmiyor — önceki dersteki "alıcı hiç yokken deneme hiçbir zaman geçmez"
ölçümünün burada tekrar görünen hâli. Kapasite `2` iken tam iki, kapasite `4` iken tam
dört deneme geçiyor: `gecen` sayısı her durumda **tam olarak kapasiteye** eşit, beşinci
denemeye hiç ulaşmadan. Beş deneme yerine elli deneme yapılsaydı sonuç değişmezdi —
tampon dolduktan sonraki her deneme, alıcı gelene kadar aynı şekilde `default` dalına
düşer.

Bu, tamponun ne yaptığını tam olarak gösteriyor: engellemeyi ortadan kaldırmıyor,
engellemenin başladığı noktayı **kapasite kadar** erteliyor. Sıfır kapasitede erteleme
hiç yok; kapasite arttıkça gönderen taraf, hiçbir alıcı olmadan yapabileceği gönderim
sayısını da artırıyor.

Bu erteleme, göndericinin alıcıdan **bağımsız** çalışabildiği pencerenin büyüklüğüdür.
Kapasite `0` iken bu pencere yoktur, gönderen her adımda alıcıyı beklemek zorundadır.
Kapasite büyüdükçe gönderen, alıcının hızına bakmadan art arda o kadar adım atabilir —
ama pencere kapandığında (`kacTaneGeciyor` ölçümünde beşinci denemeden itibaren) davranış
yeniden tamponsuz kanaldakiyle aynıya döner.

#### len ve cap Tamponun Anlık Durumunu Gösterir

Bir kanalın o anki doluluğu programın içinden de okunabilir: yerleşik `len` işlevi
tamponda bekleyen, henüz alınmamış değer sayısını döndürür; `cap` işlevi ise kanalın
sabit kapasitesini. İkisi birbirinden bağımsız bilgi taşır — `cap` bir kanal oluşturulduğu
andan itibaren hiç değişmez, `len` her gönderim ve alımda değişir.

```go
// lencap.go — kanalin len'i o an tamponda bekleyen deger sayisini, cap'i kapasiteyi verir
package main

import "fmt"

func main() {
	kanal := make(chan int, 3)
	fmt.Println("bos        : len=", len(kanal), "cap=", cap(kanal))

	kanal <- 1
	kanal <- 2
	fmt.Println("iki dolu   : len=", len(kanal), "cap=", cap(kanal))

	<-kanal
	fmt.Println("biri alindi: len=", len(kanal), "cap=", cap(kanal))
}
```

```
bos        : len= 0 cap= 3
iki dolu   : len= 2 cap= 3
biri alindi: len= 1 cap= 3
```

**GK10.** — Kanal boşken `len` sıfır, iki gönderimden sonra iki, bir alımdan sonra bir
oluyor; `cap` üçü hiç değişmeden koruyor, çünkü kapasite `make` anında sabitleniyor ve
sonradan değiştirilemiyor. `len(kanal) == cap(kanal)` olduğunda kanal doludur ve bir
sonraki gönderim (bu ders boyunca ölçülen tam olarak bu nokta) beklemeden geçemez;
`len(kanal) == 0` olduğunda kanal boştur ve bir alım aynı şekilde bekler. Bu iki işlev,
önceki bölümdeki `kacTaneGeciyor` ölçümünün içeriden bakan hâli: orada dışarıdan deneme
sayısı sayılıyordu, burada kanalın kendi durumu doğrudan okunuyor.

Bu işlevlerin bir sınırı var: `len` yalnızca **o an** okunduğu andaki değeri verir ve
birden çok goroutine aynı kanala aynı anda erişiyorsa bu değer okunduğu anda bile değişmiş
olabilir. Tek goroutine'in kullandığı bir kanalda `len` güvenilir bir gözlemdir; paylaşılan
bir kanalda yalnızca bir anlık fotoğraftır, karar vermek için kullanılmaz. Örneğin
`len(kanal) < cap(kanal)` doğruyken bir gönderim yapmaya karar vermek güvenli değildir:
bu okuma ile gönderimin kendisi arasında başka bir goroutine aynı kanala yazabilir ve
tampon okuma anından sonra dolabilir; bu durumda gönderim yine de bekler, `len`in az
önce verdiği "yer var" bilgisi geçerliliğini o iki satır arasında yitirmiş olur. `len` ve
`cap` bu yüzden bir karar aracı değil, yalnızca bir gözlem aracıdır.

#### Bir Yer Açılınca Deneme Yeniden Geçer

Tamponun doluluğu sabit değildir — bir alıcı tampondan bir değer çektiğinde açılan yer,
daha önce engellenen bir denemenin şimdi geçmesini sağlar. Bu da tek bir goroutine
içinde, sırayla, hiçbir eşzamanlılık gerektirmeden gözlemlenebilir.

```go
// alanAcilinca.go — tamponlu kanalda alici bir deger cekince kapanan yer tekrar aciliyor
package main

import "fmt"

func denenirMi(kanal chan int, v int) bool {
	select {
	case kanal <- v:
		return true
	default:
		return false
	}
}

func main() {
	kanal := make(chan int, 2)
	kanal <- 1
	kanal <- 2

	ucuncuDenemeDoluyken := denenirMi(kanal, 3)
	fmt.Println("tampon doluyken ucuncu deneme gecti mi:", ucuncuDenemeDoluyken)

	alinanDeger := <-kanal
	fmt.Println("alinan deger                          :", alinanDeger)

	ucuncuDenemeSonra := denenirMi(kanal, 3)
	fmt.Println("yer acilinca ayni deneme gecti mi      :", ucuncuDenemeSonra)
}
```

```
tampon doluyken ucuncu deneme gecti mi: false
alinan deger                          : 1
yer acilinca ayni deneme gecti mi      : true
```

**GK11.** — Kapasitesi `2` olan kanal iki değerle dolduruluyor; üçüncü deneme `default`'a
düşüyor çünkü tampon dolu. `<-kanal` ile bir değer çekilince (`1`, çünkü kanal bir
kuyruk gibi giriş sırasını koruyor — bu FIFO sıralaması tamponlu kanalın tanımının bir
parçası, ayrı bir ölçüm değil) tamponda bir yer açılıyor. Aynı üçüncü deneme, kaynak
kodu hiç değişmeden, şimdi `true` dönüyor. Kapasite sabit kalıyor; değişen şey
kapasitenin **doluluk oranı**.

Buraya kadarki iki ölçüm `select`+`default` ile **denedi**; deneme başarısız olduğunda
gönderim hiç yapılmıyor, program hemen bir sonraki satıra geçiyor. Ama tamponun dolmasının
asıl sonucu, `select` kullanılmadan doğrudan yazılan bir gönderimin **gerçekten
beklemesidir** — önceki derste tamponsuz kanalda görülen davranışın aynısı. Bunu tek bir
goroutine içinde göstermek mümkün değil, çünkü bloke olan taraf kendi bloke olduğunu
gözlemleyemez; ama bloke olmuş bir gönderimin, yer açılınca **kesinlikle tamamlandığını**
ayrı bir sinyalle doğrulamak mümkün.

```go
// blokluyor.go — kapasite dolunca blocking gonderim gercekten bekliyor, alici cekince tamamlaniyor
package main

import "fmt"

func main() {
	kanal := make(chan int, 1)
	kanal <- 1

	ikinciTamamlandi := make(chan struct{})
	go func() {
		kanal <- 2
		close(ikinciTamamlandi)
	}()

	ilkAlinan := <-kanal
	<-ikinciTamamlandi
	ikinciAlinan := <-kanal
	fmt.Println("ilk alinan   :", ilkAlinan)
	fmt.Println("ikinci alinan:", ikinciAlinan)
}
```

```
ilk alinan   : 1
ikinci alinan: 2
```

**GK12.** — Kapasitesi `1` olan kanal tek değerle (`1`) dolduruluyor. Başlatılan
goroutine'in `kanal <- 2` satırı, tampon dolu olduğu için hemen tamamlanamaz ve orada
bekler — ana goroutine bunu doğrudan gözlemlemiyor, çünkü gözlemlemeye çalışmak (örneğin
`select`+`default` ile "hâlâ bloke mi" diye sormak) kendi başına bir yarış olurdu. Bunun
yerine ana goroutine önce tampondaki tek değeri çekiyor (`ilkAlinan`), bu da ikinci
goroutine'in bekleyen gönderimine yer açıyor; ardından `<-ikinciTamamlandi` ile o
gönderimin **kesinlikle** tamamlandığını bekliyor. `close(ikinciTamamlandi)` çağrısı
`kanal <- 2` satırından **sonra** çalıştığı için, bu kapanmayı görmek, gönderimin de
gerçekleştiğini garanti ediyor. Son alım (`ikinciAlinan`) bu yüzden her koşumda `2`.

#### Sınırlayıcı Ölçüm: Tampon Eşzamanlamayı Kaldırmaz, Erteler

Önceki iki ölçüm tamponun **ne zaman** engellediğini gösterdi. Bu bölüm tamponun **neyi
söylemediğini** ölçüyor: bir tamponlu kanala yapılan gönderim tamamlandığında, gönderen
taraf o değerin gerçekten okunduğunu bilmiyor — yalnızca tamponda yer bulduğunu biliyor.
Tamponsuz bir kanalda bu iki bilgi aynı satırda gelirdi, çünkü gönderim ancak alıcı hazır
olduğunda tamamlanırdı. Tamponlu bir kanalda bu iki an ayrışıyor.

```go
// ertelenir.go — tamponlu kanalda gonderim tamamlanir ama alicinin aldigini bildirmez
package main

import "fmt"

func main() {
	kanal := make(chan int, 1)
	onay := make(chan struct{})

	kanal <- 42
	fmt.Println("gonderim tamamlandi (tamponlu)")

	go func() {
		<-kanal
		close(onay)
	}()

	<-onay
	fmt.Println("alicinin aldigini simdi ogrendik (ayri sinyalle)")
}
```

```
gonderim tamamlandi (tamponlu)
alicinin aldigini simdi ogrendik (ayri sinyalle)
```

**GK13.** — `kanal <- 42` satırı, kapasite dolu olmadığı için hiç beklemeden tamamlanıyor
ve hemen ardından "gönderim tamamlandı" satırı basılıyor — bu noktada henüz hiçbir alıcı
goroutine bile başlamadı. Alıcının değeri gerçekten okuduğunu öğrenmenin **tek yolu**,
ayrı bir `onay` kanalıdır; kod bunu kullanmasaydı, gönderen tarafın alımın ne zaman
olduğunu (hatta olup olmadığını) bilmesinin hiçbir yolu kalmazdı. Bu, tamponlu bir kanalın
gönderme tarafına verdiği garantiyle sınırını aynı anda gösteriyor: garanti yalnız "tampon
bunu tuttu", alım hakkında hiçbir söz içermiyor.

Bu fark, hangi kanalın seçileceği kararının bir mühendislik tercihi olduğunu gösteriyor.
Gönderenin alımın gerçekleştiğini bilmesi gerekiyorsa — örneğin bir işin başladığını
onaylamak için — tamponsuz bir kanal ya da ayrı bir onay sinyali gerekir. Gönderenin
yalnızca değeri bırakıp devam etmesi yeterliyse ve alıcının o anda hazır olması
gerekmiyorsa, tampon göndereni bekletmeden ilerlemesini sağlar. İkisi de "eşzamanlama"
yapıyor, ama farklı anlarda ve farklı bilgiyle.

Bu ayrım, "hazır olduğunu bilmek" ile "hazır olacağından emin olmak" arasındaki farkın bir
örneği. `onay` kanalı olmasaydı, `ertelenir.go`daki gönderen taraf değeri tampona
bıraktıktan sonra hiçbir şey öğrenmeden devam ederdi — ne alıcının çalıştığını, ne ne
zaman çalışacağını, ne de hiç çalışıp çalışmayacağını. Tampon kapasitesi büyüdükçe bu
belirsizlik ortadan kalkmıyor, yalnızca kaç gönderimin bu belirsizlikle bırakılabileceği
büyüyor: kapasitesi `100` olan bir kanala art arda yüz gönderim yapmak, her biri ayrı ayrı
"tamamlandı" der ama bunların kaçının gerçekten okunduğu konusunda tek bir bilgi vermez.

Tamponlu bir kanalın bu sessizliği hata değildir; kanalın sözleştiği şey zaten yalnızca
"bu değeri sakla", "alıcının onu aldığını bildir" değildir. İkinci bilgiyi isteyen kod,
tıpkı bu ölçümde olduğu gibi, onu ayrıca kurmak zorundadır.

Bu dersin dört ölçümü tek bir çizgide birleşiyor. `kapasite.go` kapasitenin bir sınır
olduğunu, o sınıra kadar hiç beklemeden geçilebildiğini gösterdi. `lencap.go` bu sınırın
ve o anki doluluğun programın içinden okunabildiğini gösterdi. `alanAcilinca.go` ve
`blokluyor.go` sınırın aşılmasının iki farklı görünümünü verdi: biri denemenin
başarısız olup hemen devam ettiğini, öteki gerçek bir gönderimin yer açılana kadar
gerçekten beklediğini. `ertelenir.go` ise bu dört ölçümün hiçbirinin, gönderimin
**alındığı anı** söylemediğini gösterdi — kapasite, doluluk, engellenme, hepsi
göndericinin kendi tarafında ölçülebilen şeyler; alım tarafının ne zaman gerçekleştiği
bunların dışında kalıyor.

#### Özet

- Kanalın kapasitesi, engellemenin başladığı noktayı belirler: ilk `kapasite` kadar
  gönderim hiç beklemeden geçer, sonrakiler alıcı gelene kadar bekler.
- Kapasitesi sıfır olan kanal ayrı bir tip değildir; tamponsuz kanal, kapasitesi sıfır
  olan kanalın kendisidir.
- Bir alıcı tampondan bir değer çektiğinde açılan yer, daha önce engellenmiş bir denemenin
  şimdi geçmesini sağlar.
- Tamponlu bir kanala gönderim tamamlanmış olmak, alıcının o değeri okuduğu anlamına
  gelmez; bu bilgi yalnız ayrı bir sinyalle elde edilebilir.
- Tamponsuz ve tamponlu kanal arasındaki seçim, gönderenin alımın gerçekleştiğini bilmesi
  gerekip gerekmediğine bağlı bir tasarım kararıdır.
- `len` bir kanalda o an bekleyen değer sayısını, `cap` sabit kapasiteyi verir; ikisi
  birlikte kanalın anlık doluluğunu, ayrıca bir deneme yapmadan okumayı sağlar.

#### Sonraki Adım

Bu derste ölçülen iki kanal türü de yalnızca bir tek kanalı bekliyordu; farkları o kanalın
ne zaman beklettiğindeydi. `select`+`default` bu derste yalnızca **tek bir** kanal
üzerinde bir deneme aracı olarak kullanıldı — kapasitenin dolu olup olmadığını yoklamak
için. Ama bir goroutine çoğu zaman birden çok kaynaktan aynı anda haber bekler: birkaç
sonuç kanalından biri, bir iptal sinyali, bir zaman aşımı. Sıradaki ders `select`in bu
geniş biçimini ölçüyor — birden çok kanal aynı `select` içinde birlikte beklendiğinde
hangi dal hazırsa o seçiliyor ve hiçbiri hazır değilse ne oluyor.

### select Deyimi

Source: https://academia.sh/tr/kurslar/go-eszamanlilik/select-deyimi

Birden çok kanal üzerinde aynı anda beklemenin karşılığı select deyimidir: yalnız bir dal hazırsa o dal belirlenimci biçimde seçilir, hiçbiri hazır değilse ve bir default dalı varsa o dal seçilir. Yazım sırası bir öncelik taşımaz. Sınırlayıcı ölçüm: iki dal aynı anda hazırsa seçim rastgeledir ve bu durum koşturulmuyor, yalnızca modelleniyor — rastgelelik dilin kendi kararı.

Önceki ders bir kanalın kapasitesinin engellemeyi nerede başlattığını ölçtü ve `select` ile
`default`'u yalnız **tek bir** kanalı yoklamak için kullandı. Ama bir goroutine çoğu zaman
birden çok kaynaktan aynı anda haber bekler: birkaç sonuç kanalından biri hazır olabilir, bir
iptal sinyali gelebilir, bir zaman aşımı dolabilir. `select` deyimi bunun için var — birden
çok kanal üzerinde aynı anda bekleyip **hangisi önce hazır olursa** onu işleyen tek yapı.

Bu ihtiyaç, tek bir kanalı beklemenin yetmediği her yerde ortaya çıkıyor. Bir goroutine
yalnız bir kanalı beklerken `<-kanal` yazmak yeterlidir; ama iki ayrı kaynaktan **hangisi
önce** gelirse onu işlemek isteyen bir goroutine, bunu tek bir alım ifadesiyle yazamaz.
`select` tam olarak bu boşluğu dolduruyor.

Bir `select` bloğu, her biri bir kanal işlemiyle başlayan birden çok `case` içerir. Deyim bu
işlemlerden hazır olanlardan birini seçip o dalı çalıştırır; hiçbiri hazır değilse ve bir
`default` dalı varsa hemen o çalışır, yoksa `select` en az bir dal hazır olana kadar bekler.
Bu ders bu seçimin nasıl belirlendiğini, nerede belirlenimci nerede rastgele olduğunu ölçüyor.

`select`in her dalı bir gönderim ya da bir alım olabilir; aynı `select` içinde bazı dallar
gönderim, bazıları alım olabilir, önemli olan yalnızca hangisinin **o an** engellemeden
tamamlanabileceğidir. Bu, `select`i bir `switch`ten ayıran ilk fark: `switch` bir değeri
sırayla dallarla karşılaştırırken, `select` her dalın kendi kanal işlemini **aynı anda**
dener ve hangisi hazırsa ona göre karar verir. Bir kanal işlemi olmayan bir `case` yazmak
derlenmez; `select`in dalları her zaman bir gönderim ya da alımla başlar.

Bir `select`in kaç dalı olabileceğine dilde bir üst sınır yok; bir `select` bloğu iki
kanalı da, on kanalı da bekleyebilir. Bu ders yalnızca iki kanallı örnekler kullanıyor,
çünkü ölçülen kurallar — belirlenimli seçim, sıra bağımsızlığı, rastgelelik — dal
sayısından bağımsız; iki dal bu kuralları göstermek için yeterli ve okunması en kolay
durum. Dal sayısı arttıkça ölçülen kurallar değişmiyor, yalnızca aynı anda hazır
olabilecek dal sayısı artıyor; üçüncü bölümdeki rastgelelik kuralı da ikiden fazla dal
hazır olduğunda aynı biçimde geçerli kalıyor.

#### Tek Dal Hazırken Seçim Belirlenimlidir

Bir `select` içindeki dallardan yalnız biri hazırsa, `select` başka hiçbir seçenek
düşünmeden o dalı çalıştırır. Hiçbiri hazır değilse ve bir `default` dalı varsa, `select`
hiç beklemeden o dala düşer — önceki derste kapasiteyi yoklamak için kullanılan tam olarak
bu davranıştı.

```go
// tekilVeVarsayilan.go — yalniz bir dal hazirken secim belirlenimli, hicbiri hazir degilken varsayilan dal secilir
package main

import "fmt"

func sec(a, b chan string) string {
	select {
	case v := <-a:
		return "a: " + v
	case v := <-b:
		return "b: " + v
	default:
		return "varsayilan: hicbiri hazir degil"
	}
}

func main() {
	a := make(chan string, 1)
	b := make(chan string, 1)

	fmt.Println("hicbiri hazir degilken:", sec(a, b))

	a <- "ilk deger"
	fmt.Println("yalniz a hazirken     :", sec(a, b))

	b <- "ikinci deger"
	fmt.Println("yalniz b hazirken     :", sec(a, b))
}
```

```
hicbiri hazir degilken: varsayilan: hicbiri hazir degil
yalniz a hazirken     : a: ilk deger
yalniz b hazirken     : b: ikinci deger
```

**GK14.** — Üç çağrı da aynı `sec` fonksiyonunu, aynı kaynak koduyla çağırıyor; değişen
tek şey kanalların doluluğu. İlk çağrıda ikisi de boş, `default` seçiliyor. `a`ya bir
değer konduğunda ikinci çağrı `a` dalını seçiyor ve bu arada `a`daki değeri **tüketiyor**
— `a` şimdi yine boş. Üçüncü çağrıda yalnız `b` doluyken `b` dalı seçiliyor. Hiçbir
çağrıda birden fazla dal aynı anda hazır olmadı; bu yüzden her üç sonuç da kesinlikle
tekrarlanabilir.

Bu ölçümün tek goroutine içinde, hiç eşzamanlılık gerektirmeden yapılabilmesi önemli: `a`
ve `b` kanallarına doğrudan ana goroutine'den değer konuluyor, hiçbir goroutine
başlatılmıyor. Bu, `select`in kendisinin eşzamanlılığa özgü bir yapı olmadığını gösteriyor
— `select` yalnız birden çok kanal işlemi arasında seçim yapan bir dildir; kanalların
karşı tarafında goroutine olup olmaması `select`in kendi davranışını değiştirmiyor. Sonraki
derslerde `select` hep goroutine'ler arasında kullanılacak, ama bu ders onun çekirdek
davranışını en yalın hâliyle, tek goroutine'de gösteriyor.

Bu ayrım küçük görünse de önemli: `select`in belirlenimli davranışı zamanlamadan değil,
yalnızca **hangi kanalların o an dolu olduğundan** kaynaklanıyor. Aynı kod, kanalları
farklı bir sırayla doldursaydı ya da farklı bir makinede çalıştırılsaydı sonuç yine aynı
olurdu — çünkü belirleyici olan zamanlama değil, doluluk durumunun kendisi.

#### Yazım Sırası Bir Öncelik Değildir

`select`in dalları yukarıdan aşağı bir öncelik sırası taşımaz — bu, sıradan bir `if`/`else
if` zincirinden ya da bir `switch`ten temel bir farktır. Hazır olan tek dal hangi sırada
yazılmış olursa olsun seçilir; yazım sırası yalnızca okunabilirlik içindir, çalışma zamanı
davranışını etkilemez.

```go
// siraOnemsiz.go — select dallarinin yazim sirasi hangi dalin secilecegini belirlemez
package main

import "fmt"

func sec(a, b chan string) string {
	select {
	case v := <-b:
		return "b: " + v
	case v := <-a:
		return "a: " + v
	}
}

func main() {
	a := make(chan string, 1)
	b := make(chan string, 1)
	a <- "sadece a doldu"

	fmt.Println(sec(a, b))
}
```

```
a: sadece a doldu
```

**GK15.** — Bu fonksiyonda `b` dalı kaynakta **önce**, `a` dalı **sonra** yazılı. Yalnız
`a` doluyken çalıştırıldığında sonuç yine `a: sadece a doldu` — kaynaktaki sıralamanın
sonucu değiştirmediğini gösteriyor. `select`in her `case`i eşit ağırlıkta değerlendirilir;
hazır olan tek dal neyse o çalışır. Bu, bir sonraki bölümün sorusunu doğuruyor: iki dal
**aynı anda** hazır olsaydı ne olurdu, madem sıralama bir öncelik vermiyor?

Bu davranış, `select`in dallarını değerlendirme biçimiyle ilgili. Kaynakta yazılan sıra
yalnızca derleyicinin `case`leri okuma sırasıdır; çalışma zamanında `select`, çalıştırılacağı
anda dalların **tümünü** birden değerlendirip hazır olanları bulur, sonra bunlar arasından
seçer. Sıradan bir `if a != nil { ... } else if b != nil { ... }` zincirinde ilk koşulu
sağlayan dal kazanırdı — orada yazım sırası gerçekten bir önceliktir. `select` bu modele
uymuyor; bu fark, `select`i öğrenirken en sık karışan noktalardan biri, çünkü sözdizimi
`switch`e çok benziyor ama değerlendirme kuralı farklı.

#### Sınırlayıcı Ölçüm: İki Dal Birden Hazırsa Seçim Rastgeledir

Buraya kadarki her ölçüm, herhangi bir anda **en fazla bir** dalın hazır olduğu durumları
kurdu. İki dal aynı anda hazır olduğunda dil hangi kuralı uygular? Go'nun kendi belirtimi
bu soruya açık bir yanıt veriyor: birden çok dal aynı anda hazırsa, `select` aralarından
**tekdüze rastgele** birini seçer. Bu, kursun bu derse kadarki her ölçümünün dayandığı
belirlenimcilik kuralının **bilinçli bir istisnası** — ve bu yüzden bu ders bu durumu
**koşturmuyor**, yalnızca modelliyor.

```go
// modellenir.go — iki dal ayni anda hazirsa secim rastgeledir; bu ders bu dali kosturmaz, yalniz gosterir
package main

func ikiDalBirdenHazirsaSecim(a, b chan string) string {
	select {
	case v := <-a:
		return "a: " + v
	case v := <-b:
		return "b: " + v
	}
}
```

**GK16.** — Bu fonksiyon, `a` ve `b`nin ikisi de dolu çağrıldığında `select`in tam olarak
karşılaştığı durumu gösteriyor: derleyici burada iki dal arasında sabit bir tercih
yapmıyor, çalışma zamanı her çağrıda yeniden rastgele seçiyor. `ikiDalBirdenHazirsaSecim`
bu dersin hiçbir yerinde çağrılmıyor — çağrılsaydı, iki koşum arasında farklı sonuçlar
üretebilirdi ve bu, kod_dogrula'nın aradığı "her koşumda aynı çıktı" koşulunu bilerek
bozardı. Fonksiyon yalnızca **var olduğu için** derleniyor; kaynağın kendisi rastgeleliğin
nerede ortaya çıktığını gösteriyor, koşumu değil.

Bu rastgelelik bir eksiklik değil, bilinçli bir tasarım kararı. Eğer `select` iki hazır
dal arasında hep kaynaktaki ilk dalı seçseydi, programcılar zamanla bu sırayı bir öncelik
kuralı gibi kullanmaya başlar ve kanallardan birinin sistematik olarak açlığa
düşmesine yol açabilirdi — art arda aynı iki dal sürekli hazır kaldığında, ikinci dal
hiç çalışmayabilirdi. Rastgele seçim bu riski dilin kendisi ortadan kaldırıyor: hiçbir
dal, yalnızca kaynakta önce yazıldığı için sistematik olarak öne geçemiyor.

Bu ölçülemezlik, önceki bölümdeki "yazım sırası bir öncelik değildir" bulgusunun asıl
gerekçesi. Sıra bir öncelik taşısaydı, iki dal aynı anda hazır olduğunda hangisinin
seçileceği yine belirlenimci olurdu — ilk yazılan kazanırdı. Sıranın hiçbir ağırlığı
olmadığı için, iki dal birden hazır olduğunda gerçekten seçilecek bir kural kalmıyor;
dil bu boşluğu rastgelelikle dolduruyor, M09/K01 Go Temelleri kursunda görülen "tanımlı"
sınıfından farklı bir yol: orada yazılmayan bir şeyin yerine **sabit** bir değer
konuyordu, burada yazılmayan bir şeyin yerine her seferinde **yeniden çekilen** bir
değer konuyor. Bu ders bu ayrımı bir eksen olarak sürdürmüyor, yalnız bir kez not ediyor.

Bu ders `ikiDalBirdenHazirsaSecim`i hiç çağırmadığı için, iki koşum arasındaki farkı
göstermenin tek yolu kaynağı okumak. Bu, kursun M09/K03'e kadarki bütün derslerinin
aksine, "aynı kaynak farklı koşumlarda farklı sonuç verebilir" iddiasının **koşturmadan**
kanıtlandığı tek yer: rastgeleliğin varlığını Go'nun kendi belirtimi garanti ediyor, bu
ders yalnız o garantinin nerede devreye girdiğini kaynak üzerinde gösteriyor.

#### select Bloklamayan Bir Denetim Aracıdır

`default` dalı `select`i yalnızca bir yoklama aracına değil, akışı hiç durdurmadan devam
ettiren bir denetim aracına dönüştürüyor. Bu, bir kanaldaki bütün hazır değerleri, hiçbiri
kalmayana kadar tüketmek için kullanılabilir — kanal boşaldığı anda döngü, beklemeden
kendini durdurur.

Bu kullanım, `select`in iki farklı işlevini birbirinden ayırıyor. Birinci bölümdeki
kullanımda `select` birden çok kanal arasında **seçim** yapıyordu — hangi kaynağın hazır
olduğunu belirliyordu. Burada `select` tek bir kanal üzerinde, o kanalın hazır olup
olmadığını **sormak** için kullanılıyor; `default` dalının varlığı, "hazır değilse bekleme"
demenin dildeki karşılığı. İki kullanım da aynı sözdizimini paylaşıyor, ama amaçları
farklı: biri birden çok kaynak arasında karar veriyor, öteki tek bir kaynağın anlık
durumunu okuyor.

```go
// engellenmeyenDenetim.go — select, hazir olmayan bir kanali beklemeden akisi surdurmek icin kullanilir
package main

import "fmt"

func hazirsaAl(kanal chan int) (int, bool) {
	select {
	case v := <-kanal:
		return v, true
	default:
		return 0, false
	}
}

func main() {
	sonuclar := make(chan int, 3)
	sonuclar <- 10
	sonuclar <- 20

	toplam, adet := 0, 0
	for {
		v, tamam := hazirsaAl(sonuclar)
		if !tamam {
			break
		}
		toplam += v
		adet++
	}
	fmt.Println("kanaldan engellenmeden alinan deger sayisi:", adet, "toplam:", toplam)

	_, tamam := hazirsaAl(sonuclar)
	fmt.Println("kanal bosalinca uzerine deneme            :", tamam)
}
```

```
kanaldan engellenmeden alinan deger sayisi: 2 toplam: 30
kanal bosalinca uzerine deneme            : false
```

**GK17.** — `sonuclar` kanalına yalnızca iki değer konuldu; döngü bu ikisini `hazirsaAl`
ile tüketiyor ve üçüncü denemede `tamam` `false` döndüğü için döngü kendiliğinden
duruyor — hiçbir goroutine beklenmedi, hiçbir sıra çizelgeleyiciye bırakılmadı. Bu
düzenek, bir kanalın "şu an elimde ne var" diye sorulmasını sağlıyor; kanalın kendisi
boşsa, soran taraf beklemeden yoluna devam edebiliyor. Bu, önceki derste tampon
kapasitesini yoklamak için kullanılan tekniğin genel biçimi: orada tek bir kanalın dolu
olup olmadığı soruluyordu, burada aynı teknik bir kanalı **tüketmek** için kullanılıyor.

Döngünün durma koşulu da dikkat çekici: `for` döngüsü sabit bir sayıya göre değil,
`hazirsaAl`in döndürdüğü ikinci değere göre duruyor. Kaynak koddan kaç kez döneceği
okunamaz — `sonuclar` kanalına kaç değer konulmuş olursa olsun aynı döngü çalışır ve
tam olarak o kadar adımda durur. Bu, `range` ile bir dilim gezmekten farklı: `range`in
kaç kez döneceği dilimin uzunluğundan **önceden** okunabilirken, bu döngünün kaç kez
döneceği yalnızca koşarak öğrenilir. Bu fark, kapanış dersinin ölçtüğü `range over
channel` davranışıyla karşılaştırıldığında daha da netleşiyor: `range` bir kanalın
**kapanmasını** bir durma koşulu sayarken, buradaki döngü kanalın o anki **doluluğunu**
bir durma koşulu sayıyor — ikisi de sonlu bir döngü üretir, ama farklı bir sinyale göre.

Bu kullanım biçiminin bir bedeli var: `hazirsaAl` bir goroutine'in üretmesini **beklemez**,
yalnızca o anda hazır olanı alır. Kanala henüz hiçbir şey yazılmamışsa, `hazirsaAl` her
zaman `false` döner — üretici goroutine bir mikrosaniye sonra bir değer gönderecek olsa
bile. Bloklamayan bir denetim aracı, karşılığında beklemeyi de kaybeder; hangisinin
gerektiği, ne zaman `select`in beklemesine izin verileceği, ne zaman hemen devam etmesi
gerektiğine bağlı bir tasarım kararı.

Bu tehlike, `default` dalını bir döngü içine koymanın yanlış kullanımıyla daha da
belirginleşiyor: eğer amaç bir değerin gelmesini beklemekse ve döngü `default` dalına her
düştüğünde hemen tekrar denerse, program hiçbir zaman bloklanmadan, işlemciyi sürekli
meşgul ederek bekler — bu, kilit ve kritik bölge bağlamında M01/K05 İşletim Sistemi
Kavramları kursunda kurulmuş **meşgul bekleme** kalıbının `select` üzerindeki karşılığı.
Bu ders o kalıbı kurmuyor: `hazirsaAl` bir kez çağrılıyor, bir döngüde beklenmiyor.
Beklemek gerektiğinde doğru araç `default`suz bir `select`tir — o zaman `select` kendisi
hazır bir dal olana kadar bloklanır ve işlemciyi meşgul etmez; bu, birinci bölümdeki
`sec` fonksiyonunun `default` dalı olmadan yazılmış hâli olurdu.

#### Özet

- `select`, birden çok kanal işleminden yalnızca **bir tek** dalın hazır olduğu durumda
  o dalı belirlenimci biçimde seçer.
- Dalların kaynaktaki yazım sırası bir öncelik taşımaz; hazır olan dal hangi sırada
  yazılmış olursa olsun seçilir.
- Hiçbir dal hazır değilken bir `default` dalı varsa hemen çalışır; `select` bu şekilde
  hiç beklemeyen bir yoklama ve denetim aracına dönüşür.
- İki dal aynı anda hazır olduğunda seçim tekdüze rastgeledir; bu kursun ölçtüğü
  belirlenimcilik kuralının bilinçli istisnasıdır ve bu yüzden koşturulmaz, yalnız
  modellenir.
- `select`+`default` bir kanalı, hiçbir goroutine beklemeden, elindeki değerler bitene
  kadar tüketmek için kullanılabilir.
- `default`suz bir `select` hiçbir dal hazır olmayınca bloklanır ve işlemciyi meşgul
  etmez; beklemek gerektiğinde doğru araç budur, `default`lı bir döngü değil.

#### Sonraki Adım

Bu derste kapanan bir kanalın `select` içindeki davranışına hiç değinilmedi. Kapanmış bir
kanaldan alım da tıpkı dolu bir kanaldan alım gibi bir `select` dalını hemen hazır kılar —
ama gelen yanıt, ölçülen değerin kendisi değil, kanalın **artık boş kalacağının** haberi.
Sıradaki ders bu son davranışı ölçüyor: bir kanalın kapatılması bir sonlanma sinyalidir ve
bu sinyal hem `range` hem de doğrudan alımda kendini nasıl gösteriyor.

### Kanal Kapatma ve Aralık

Source: https://academia.sh/tr/kurslar/go-eszamanlilik/kanal-kapatma-ve-aralik

Bir kanalın kapatılması bir sonlanma sinyalidir: kapanan kanalda range biter, kapanan kanaldan doğrudan alım sıfır değer ve false döner. Dört alım biçiminin (açık, kapalı-dolu, kapalı-boş, tekrar kapalı-boş) yanıtı bunu ölçüyor. Kapatma sorumluluğu her zaman göndericidedir. Sınırlayıcı ölçüm: kapalı bir kanala gönderim de, bir kanalı ikinci kez kapatmak da panikler; ikisi de recover ile yakalanıp yalnız panik=true/false olarak yazılıyor.

Önceki ders `select`in belirlenimli seçimini ve rastgele istisnasını ölçtü, ama bir soruyu
açık bıraktı: kapanmış bir kanal `select` içinde nasıl davranır? Bu ders bu soruyla açılıyor
ve konunun son sorusuna geçiyor — bir kanalın **kapatılması** neyi bildiriyor ve bu bildirim
kimin sorumluluğunda. Bu, konunun beşinci ve son dersi: goroutine'lerin nasıl durduğu, kanalların ne
aktardığı, tamponun neyi ertelediği ve `select`in nasıl seçtiği ölçüldü; geriye tek bir
soru kaldı — bir iletişim ne zaman **biter** ve bu bitişi kim ilan eder.

Bir kanal `close(kanal)` ile kapatılabilir. Kapatma bir değer göndermez; kanalın **artık
hiçbir yeni değer taşımayacağını** bildiren ayrı bir sinyaldir. Bu sinyal iki yerde
görünür: doğrudan bir alımda (`deger, tamam := <-kanal`) ve bir `range` döngüsünde. Bu
dersin ölçtüğü şey, bu iki görünümün tam olarak ne söylediği. `close` yalnızca kanallar
üzerinde tanımlıdır; başka hiçbir tip için karşılığı yoktur, çünkü bir kanalın "artık boş
kalacak" demenin gerçek bir karşılığı vardır, oysa bir tam sayının ya da bir dizginin
böyle bir sonlanma kavramı hiç yoktur.

Kapatma, bu kursun ilk dersinde sayılan dört durma biçiminden birine doğrudan bağlanıyor:
"beklediği geldi" sınıfının bir goroutine'i, beklediği şeyin bir kanal kapatması olduğunda,
kapatma anında serbest kalıyor. O derste kapanan bir kanalda `range` yapan bir goroutine
tam olarak bu yolla durmuştu; bu ders o tek satırlık gözlemi genişletiyor. Aynı dersteki
"hiç durmadı" örneklerinden biri de kapatılmayan bir kanaldı — bu ders o örneğin neden
hiç durmadığını da ayrıca ölçüyor.

#### Kapanan Kanal select İçinde Hemen Hazırdır

Önceki dersin sonunda bırakılan soru buydu: kapanmış bir kanal, hiç değer içermese bile,
bir `select` dalını hazır kılar mı? Yanıt evet — kapanmış bir kanaldan alım hiçbir zaman
engellemez, bu yüzden o dal her zaman seçilmeye **hazırdır**. Taşıdığı yanıt ise bir değer
değil, kanalın bittiğinin haberidir.

```go
// selectKapali.go — kapanan bir kanal select icinde hemen hazir olur, sifir deger ve false doner
package main

import "fmt"

func main() {
	kapali := make(chan int)
	close(kapali)

	bekleyen := make(chan int)

	select {
	case v, tamam := <-kapali:
		fmt.Println("kapali kanaldan gelen dal secildi, deger=", v, "tamam=", tamam)
	case v, tamam := <-bekleyen:
		fmt.Println("beklenen kanaldan gelen dal secildi, deger=", v, "tamam=", tamam)
	}
}
```

```
kapali kanaldan gelen dal secildi, deger= 0 tamam= false
```

**GK18.** — `bekleyen` hiçbir zaman doldurulmuyor ve hiçbir zaman kapatılmıyor; bu dal
sonsuza kadar hazır olmayacaktı. `kapali` ise `close` çağrısından itibaren **her zaman**
hazırdır — bu yüzden `select` bu iki dal arasında hiç tereddüt etmeden `kapali` dalını
seçiyor, önceki dersteki rastgelelik burada devreye girmiyor çünkü yalnız bir dal gerçekten
hazır. `v` sıfır değerinde (`0`) kalıyor ve `tamam` `false` dönüyor — bu iki yanıtlı biçim,
M09/K01 Go Temelleri kursunda sözlük erişiminde görülen aynı sözdiziminin kanallardaki
karşılığı, ama taşıdığı anlam farklı: sözlükte `tamam` bir anahtarın var olup olmadığını
söylerken, kanalda kanalın **açık olup olmadığını** söylüyor.

Bu, önceki dersteki nil kanal gözlemiyle de bağlantılı: bir `select` içinde bir dalı
etkisiz kılmanın yolu o dalın kanalını `nil`e çevirmekti, çünkü `nil` bir kanal hiçbir
zaman hazır olmuyordu. Kapalı bir kanal bunun tam tersi uçta duruyor — hiçbir zaman hazır
olmamak yerine, **her zaman** hazır. İkisi de birer sınır durum, ama zıt yönlerde: biri
sonsuza kadar bekletir, öteki hiç beklemez.

Bu iki sınır durum birlikte kullanılabiliyor da: bir goroutine, bir işi bitirdiğinde kendi
ilerleme kanalını `nil`e çevirip o daldan artık hiç seçilmemesini sağlayabilir, ya da bir
iptal kanalını kapatıp tüm bekleyenlerin aynı anda uyanmasını sağlayabilir. Bu kalıpların
ikisi de Eşzamanlılık Kalıpları konusunda işleniyor; burada yalnız uçların kendisi ölçüldü.

#### Dört Alım Biçimi

Kapatmanın taşıdığı bilgi, bir kanalın o an hangi durumda olduğuna göre değişiyor. Dört
durum birbirinden ayrılıyor: kanal açıkken bir değer alınıyor, kanal kapatıldıktan sonra
tamponda kalan bir değer alınıyor, tampon da boşaldıktan sonra alınıyor ve bu son durum
tekrarlanıyor.

```go
// dortAlim.go — kapali kanaldan alimin dort bicimi: acik, kapali-dolu, kapali-bos, araliktan cikma
package main

import "fmt"

func main() {
	kanal := make(chan int, 2)
	kanal <- 10
	kanal <- 20

	v1, ok1 := <-kanal
	fmt.Println("acikken alim           :", v1, ok1)

	close(kanal)

	v2, ok2 := <-kanal
	fmt.Println("kapali ama tamponda var :", v2, ok2)

	v3, ok3 := <-kanal
	fmt.Println("kapali ve bos           :", v3, ok3)

	v4, ok4 := <-kanal
	fmt.Println("tekrar kapali ve bos    :", v4, ok4)

	toplam, adimSayisi := 0, 0
	kanal2 := make(chan int, 3)
	kanal2 <- 1
	kanal2 <- 2
	kanal2 <- 3
	close(kanal2)
	for deger := range kanal2 {
		toplam += deger
		adimSayisi++
	}
	fmt.Println("range ile toplam ve adim:", toplam, adimSayisi)
}
```

```
acikken alim           : 10 true
kapali ama tamponda var : 20 true
kapali ve bos           : 0 false
tekrar kapali ve bos    : 0 false
range ile toplam ve adim: 6 3
```

**GK19.** — İlk alım kanal daha kapatılmadan önce yapılıyor ve olağan bir alım gibi
`true` dönüyor. `close(kanal)` çağrıldığında tamponda hâlâ bir değer (`20`) duruyor;
kapatma bu değeri **silmiyor** — ikinci alım onu `true` ile veriyor. Tampon boşaldıktan
sonraki üçüncü alım artık sıfır değer (`0`) ve `false` veriyor; dördüncü alım da aynı
yanıtı tekrarlıyor, çünkü kapalı ve boş bir kanaldan alım **sonsuza kadar** bu yanıtı
verir, bir daha asla bloklanmaz. `range` ise bu davranışı bir döngüye çeviriyor: kanal
kapandığında ve tamponu boşaldığında döngü kendiliğinden bitiyor, `break` yazmaya gerek
kalmıyor — Denetim Akışı dersinde M09/K01'de görülen "yazılmayan `break`" kalıbının
kanallardaki karşılığı.

Bu dört yanıt, kapatmanın neyi bildirdiğini tam olarak gösteriyor: kapatma **var olan
değerleri geçersiz kılmıyor**, yalnızca **yeni değer gelmeyeceğini** bildiriyor. Tamponda
bekleyen değerler kapatmadan sonra da normal şekilde alınabiliyor; sinyal yalnız tampon
tükendiğinde devreye giriyor.

Bu dört durumun sırayla ölçülmüş olması önemli: ölçüm hiçbir goroutine başlatmadan, tek bir
goroutine içinde, adım adım kuruluyor. Kapatma anında tamponda kaç değer kaldığı önceden
biliniyor (`kanal <- 10` ve `kanal <- 20` iki değer koydu, yalnız biri alındı), bu yüzden
"kapalı ama tamponda var" durumunun tam olarak bir kez görüneceği baştan kesin. Aynı ölçüm
tamponsuz bir kanalda yapılsaydı ikinci durum (kapalı ama tamponda değer var) hiç
gerçekleşmezdi — tamponsuz bir kanalda kapatma anında bekleyen bir değer olamaz, çünkü her
gönderim ancak bir alıcı hazır olduğunda tamamlanıyordu.

#### Kapatma Sorumluluğu Göndericide Olur

Bir kanalı kapatma yetkisi teknik olarak hem gönderen hem alan tarafından çağrılabilir —
`close` bir kanal değerini alan her taraf tarafından çalıştırılabilir. Ama bu yetkinin
**doğru** kullanımı tektir: yalnızca gönderen taraf kapatabilir, çünkü yalnız gönderen
tarafın "artık gönderecek değerim kalmadı" bilgisi vardır. Bir alıcının kanalı kapatması,
göndericinin elinde hâlâ göndermeyi planladığı bir değer varken kanalı kapatmış olması
riskini taşır — ve kapalı bir kanala gönderim panikler, bu dersin son bölümünün konusu.

Bu kural, kanalın kendi tipinde yazılı değil. `chan<- int` ile kısıtlanmış bir parametre
bile `close` çağırabilir — yön kısıtlaması yalnızca gönderim ile alımı ayırıyordu, kapatma
ikisinden de ayrı bir işlem ve her iki yönde de tanımlı. Kuralın kaynağı dil değil,
**bilgi**: yalnızca göndericinin ne zaman göndermeyi bitireceğini bilme ihtimali var,
alıcının böyle bir bilgisi hiçbir zaman olamaz. Bir alıcı, kendisine daha gönderim
yapılıp yapılmayacağını kaynak koddan asla kesin biçimde bilemez.

Kapatmanın hiç yapılmamasının da bir bedeli var. Kapatılmayan bir kanalda bekleyen bir
`range`, kanal hiçbir zaman kapatılmadığı sürece hiçbir zaman durmaz — M09/K03'ün ilk
dersinde ölçülen "hiç durmadı" sınıfının burada tekrar görünen hâli. O derste bu sınıfa
giren üç örnekten biri tam olarak buydu: kapanmayan bir kanalda `range` yapan bir
goroutine, programın geri kalanı bitse bile kendisi hiç bitmiyordu.

```go
// kapatilmayanRisk.go — kapatilmayan bir kanalda range hic durmaz, sizinti boolean olarak gorunur
package main

import (
	"fmt"
	"runtime"
)

func main() {
	taban := runtime.NumGoroutine()

	kapanmayan := make(chan int)
	basladi := make(chan struct{})
	go func() {
		close(basladi)
		for range kapanmayan {
		}
	}()
	<-basladi

	fmt.Println("taban sayidan fazla goroutine var mi:", runtime.NumGoroutine() > taban)
}
```

```
taban sayidan fazla goroutine var mi: true
```

**GK20.** — `basladi` kanalı yalnızca goroutine'in başladığını doğrulamak için var; asıl
ölçülen `kapanmayan` üzerindeki `range`in hiç bitmediği. Bu goroutine, `kapanmayan`
kapatılmadığı için, program sonlanana kadar bellekte kalıyor. `runtime.NumGoroutine`in
verdiği sayı hiçbir yerde basılmıyor, yalnızca tabanla karşılaştırılıp `true`/`false`ya
indirgeniyor — kursun ilk dersinde kurulan kural burada aynen sürüyor. Bu ölçüm,
kapatmanın **isteğe bağlı bir incelik değil**, bekleyen taraf varsa bir zorunluluk
olduğunu gösteriyor.

Bu ölçümün doğruluğu iki şeye dayanıyor. Birincisi, `<-basladi` alımı, goroutine'in en
azından `close(basladi)` satırına ulaştığını garanti ediyor — bu da goroutine'in henüz
dönmediğini, dolayısıyla hâlâ sayılabilir olduğunu gösteriyor. İkincisi, goroutine'in
gövdesinde `close(basladi)`den sonra tek bir satır var (`for range kapanmayan`) ve bu
satır, `kapanmayan` hiç kapatılmadığı için asla bitmiyor; goroutine'in `<-basladi`
alımından sonra dönmüş olma ihtimali yok. Bu iki gözlem birlikte, `runtime.NumGoroutine`in
her koşumda aynı yanıtı (`true`) vermesini garanti ediyor — burada da hiçbir sıra
çizelgeleyiciye bırakılmadı.

#### Sınırlayıcı Ölçüm: Kapalı Kanala Gönderim ve İkinci Kapatma Panikler

Kapatmanın kendi kuralları var ve bu kurallar ihlal edildiğinde dil sessiz kalmıyor.
Kapalı bir kanala gönderim yapmaya çalışmak panikler; aynı kanalı ikinci kez kapatmaya
çalışmak da panikler. Bu iki davranış, `recover` ile yakalanıp yalnızca `panik=true`/
`panik=false` olarak yazılıyor — yığıt izi ve hata metni basılmıyor.

```go
// kapatmaSorumlulugu.go — kapali kanala gonderim ve iki kez kapatma panikler, recover ile yakalanir
package main

import "fmt"

func panikliyorMu(f func()) (p bool) {
	defer func() {
		if recover() != nil {
			p = true
		}
	}()
	f()
	return false
}

func main() {
	kanal := make(chan int, 1)
	close(kanal)

	gonderimPanikledi := panikliyorMu(func() { kanal <- 1 })
	fmt.Println("kapali kanala gonderim panik=", gonderimPanikledi)

	ikinciKapatmaPanikledi := panikliyorMu(func() { close(kanal) })
	fmt.Println("kanali ikinci kez kapatmak panik=", ikinciKapatmaPanikledi)
}
```

```
kapali kanala gonderim panik= true
kanali ikinci kez kapatmak panik= true
```

**GK21.** — `panikliyorMu`, çağırdığı işlevi bir `defer`/`recover` çifti içinde
çalıştırıyor ve yalnız panikleyip panikelemediğini bir `bool` olarak döndürüyor —
M09/K01 Go Temelleri kursunda kurulan panik/kurtarma kalıbının aynısı. `defer` içindeki
işlev `recover()`ı çağırıyor; bir panik varsa `recover` onu durdurup değerini döndürüyor
ve `p` değişkenini `true` yapıyor, panik yoksa `recover` `nil` dönüyor ve `p` adlandırılmış
dönüş değeri olarak baştaki `false`unda kalıyor. `kanal` bir değerle
dolu bile olsa (kapasitesi `1`, ama içi boş burada) kapatıldıktan sonra her gönderim
panikliyor; bu, üçüncü bölümdeki "kapatma değerleri silmez" gözlemiyle çelişmiyor —
kapatma yalnızca **yeni** göndermeyi yasaklıyor, var olanı okumayı değil. İkinci kapatma
da aynı şekilde panikliyor, çünkü `close` bir kanalın durumunu **geri döndürülemez**
biçimde değiştiriyor; kapalı bir kanalı "tekrar kapatmak" diye bir işlem dilde tanımlı
değil.

Bu iki panik birlikte, kapatma sorumluluğunun neden tek bir tarafta toplanması gerektiğini
gösteriyor. İki ayrı goroutine aynı kanalı kapatmaya çalışırsa, ikincisi panikler. İki ayrı
goroutine aynı kanala gönderim yapıyorsa ve biri kanalı kapatırsa, ötekinin bir sonraki
gönderimi panikler. Kapatmanın **kimin işi** olduğu konusunda bir anlaşma olmadan birden
çok goroutine aynı kanalı paylaşamıyor — bu sorunun kendisi Eşzamanlılık Kalıpları
konusunda ele alınıyor.

Bu iki panik arasında da bir fark var. Kapalı kanala gönderim panikleri, kaynak kodun
her koşumda **aynı** yerde, aynı koşullarda tetiklenir — `close`dan sonra bir gönderim
varsa panik kesindir. İkinci kapatma panikleri ise genellikle iki ayrı goroutine'in aynı
kanalı kapatmaya çalıştığı bir yarış durumunun sonucudur; hangi goroutine'in **önce**
kapattığı belirlenimli olmayabilir, ama en az birinin panikleyeceği kesindir. Bu ders
ikinci durumu da tek bir goroutine içinde, art arda iki `close` çağrısıyla, belirlenimci
biçimde kuruyor — gerçek bir yarış koşturmadan aynı sonucu gösteriyor.

Panik burada bir hata gösterimi değil, bir **sözleşme ihlali** gösterimi. Kapalı bir
kanala gönderim ya da bir kanalı ikinci kez kapatmak, çalışma zamanı hatası değil,
programın kendi mantığının bir kanalın durumunu yanlış izlediğinin kanıtıdır. Bu ikisi
`recover` ile yakalanabiliyor olsa da, kursun kuralı hâlâ geçerli: panik metnini basmak
yerine yalnızca `true`/`false` yazılıyor, çünkü panik metni sürüme bağlı bir ayrıntı.

#### Özet

- Kapanmış bir kanal `select` içinde her zaman hazırdır; taşıdığı yanıt sıfır değer ve
  `false`, bir veri değil bir sonlanma haberidir.
- Kapatma var olan değerleri silmez: tamponda bekleyen değerler kapatmadan sonra da
  normal şekilde alınabilir, yalnız tampon tükendiğinde sıfır değer ve `false` başlar.
- `range`, kanal kapanıp tamponu boşaldığında kendiliğinden durur; ayrıca bir `break`
  yazmaya gerek yoktur.
- Kapatma sorumluluğu göndericidedir; kapatılmayan bir kanalda bekleyen bir `range` hiçbir
  zaman durmaz ve bu, ölçülebilir bir goroutine sızıntısıdır.
- Kapalı bir kanala gönderim ve bir kanalı ikinci kez kapatmak panikler; ikisi de
  geri döndürülemez bir durum değişikliğinin ihlalidir.
- Kapatma yetkisi kanalın tipinde değil bilgide oturur: yalnızca göndericinin ne zaman
  göndermeyi bitireceğini bilme ihtimali vardır, alıcının hiçbir zaman yoktur.

#### Sonraki Adım

Bu konunun beş dersi tek bir goroutine ile tek bir goroutine arasındaki iletişimi ölçtü:
bir kanal, iki taraf, bir gönderici, bir alıcı. Her ders bu ikili ilişkinin bir yönünü
ölçtü — goroutine'in kendisi, kanalın iki işi, tamponun ertelemesi, `select`in seçimi,
kapatmanın sinyali — ve her biri kursun açılışındaki soruya, bir goroutine'in nasıl
duracağına, bir parça yanıt ekledi.

Kapatma bir sonlanma sinyalidir ve sorumluluğu göndericidedir — ama gönderici tek
değilse, ya da alıcı tek değilse, bu sorumluluk nasıl paylaşılır? Bir kanalı birden çok
goroutine aynı anda kapatmaya çalışırsa ikincisi panikler; bu dersin son ölçümü bunu
gösterdi. Sıradaki konu bu sinyali **çok sayıda goroutine arasında** kullanan kalıplara
bakıyor: işçi havuzları, yayma ve toplama, boru hatları — ve her birinde, kapatma
sorumluluğunun kaç taraf arasında nasıl paylaşıldığı.

## Eşzamanlılık Kalıpları

Sabit sayıda işçiyle iş dağıtımının kuyruk ve sonuç kanallarına bağlanması, bir işin çoğa yayılıp sonuçların toplanması, aşamalı akış işlemede her aşamanın kendi kapatma sorumluluğu, bağlamın iptali ve istek kapsamlı değerleri taşıması, ve zaman aşımıyla son tarihlerin kaynak sızıntısını nerede önleyip nerede önlemediği.

### İşçi Havuzları

Source: https://academia.sh/tr/kurslar/go-eszamanlilik/isci-havuzlari

Sabit sayıda işçi, paylaşılan bir iş kanalından okur; kanal kapandığında hepsi aynı sebeple durur — beklediği geldi. Beş iş sekiz işçiye dağıtıldığında toplam alınan iş ve toplam rapor eden işçi sayısından, en az kaç işçinin hiç iş almadan durduğu çıkarılır. Sınırlayıcı ölçüm: işçi sayısını artırmak var olan işi bölmez, yalnız daha fazla işçinin boş kalmasına yol açar; kapatılmayan bir iş kanalında işçiler hiç durmaz.

Önceki konu kapatmanın bir sonlanma sinyali olduğunu ve bu sinyali göndermenin sorumluluğunun
gönderende durduğunu ölçtü — kapanan bir kanaldan alım sıfır değer ve `false` veriyordu, kanalda
aralık biterken. O ölçüm tek bir alıcıyla çalıştı. Bu ders aynı sinyali **çok sayıda goroutine**
arasında kullanan ilk kalıba bakıyor: sabit sayıda işçi, tek bir iş kanalını paylaşıyor ve
kanal kapandığında hepsi aynı anda değil ama aynı **sebeple** duruyor.

İşçi havuzu deseninin kendisi basit: bir iş kanalı, bir sonuç kanalı ve sabit sayıda işçi
goroutine'i. Her işçi iş kanalından okur, işi işler, sonucu sonuç kanalına yazar; iş kanalı
kapandığında okuma döngüsü biter. Yeni olan şey, önceki derste tek bir alıcının gördüğü
sonlanma sinyalinin şimdi **birden fazla goroutine'e aynı anda** ulaşması — ve her birinin bu
sinyale bağımsızca tepki vermesi. Bu dersin sorusu: sabit sayıda işçi bu paylaşılan sinyali
gördüğünde, kaçı hangi işi yapmış olarak duruyor ve işçi sayısı iş sayısını aştığında ne
oluyor?

Havuzun kendisi tek bir düşünceye dayanıyor: işçi sayısı sabit tutulursa, kaynak kullanımı da
sabit kalır — kaç iş gelirse gelsin, aynı anda çalışan goroutine sayısı hiç değişmiyor. Bu,
her iş için ayrı bir goroutine başlatmanın (bir öncekinde görülmeyen ama akla gelebilecek bir
alternatif) tam tersi bir tasarım kararı: iş sayısı denetimsiz büyüdüğünde goroutine sayısı da
sınırsız büyümesin diye, işçi sayısı en baştan sabitleniyor ve iş kanalı bu sabit sayı ile
büyüyen iş kümesi arasında bir tampon görevi görüyor. Bu tamponun nasıl tükendiğini —
hangi işçinin ne kadar aldığını değil, hepsinin aynı anda mı yoksa farklı sebeplerle mi
durduğunu — ölçmek bu dersin konusu.

#### Sabit Sayıda İşçi, Paylaşılan Bir Kuyruk

Aşağıdaki ölçüm beş işi sekiz işçiye dağıtıyor. Her işçi aynı `isler` kanalından okuyor,
kareledigi değeri `sonuclar` kanalına yazıyor ve kendi payına düşen iş bittiğinde — yani
`isler` kanalı kapanıp döngüsü sona erdiğinde — bir rapor gönderiyor. Rapor tek bir sebep
taşıyor: **beklediği geldi**. Hiçbir işçi kendi kararıyla durmuyor; hepsi aynı olayı, kanalın
kapanmasını, bekliyor.

```go
// havuz.go — sabit sayida isci, paylasilan bir is kanalindan okur; kanal kapaninca hepsi ayni sebeple durur
package main

import (
	"fmt"
	"sort"
	"sync"
)

func isci(id int, isler <-chan int, sonuclar chan<- int, raporlar chan<- string, grup *sync.WaitGroup) {
	defer grup.Done()
	for is := range isler {
		sonuclar <- is * is
	}
	// Bekleme grubu (sync.WaitGroup) burada arac olarak kullaniliyor; kendi dersi
	// kursun ilerleyen bir konusunda.
	raporlar <- "bekledigi geldi"
}

func main() {
	const isSayisi = 5
	const isciSayisi = 8

	isler := make(chan int, isSayisi)
	sonuclar := make(chan int, isSayisi)
	raporlar := make(chan string, isciSayisi)
	var grup sync.WaitGroup

	for id := 1; id <= isciSayisi; id++ {
		grup.Add(1)
		go isci(id, isler, sonuclar, raporlar, &grup)
	}

	for i := 1; i <= isSayisi; i++ {
		isler <- i
	}
	close(isler)

	grup.Wait()
	close(sonuclar)
	close(raporlar)

	fmt.Println("-- 5 is, 8 isci: kapatilan is kanali --")
	raporSayisi := 0
	tumuGeldiMi := true
	for r := range raporlar {
		raporSayisi++
		if r != "bekledigi geldi" {
			tumuGeldiMi = false
		}
	}

	sonucListesi := make([]int, 0, isSayisi)
	for s := range sonuclar {
		sonucListesi = append(sonucListesi, s)
	}
	sort.Ints(sonucListesi)
	toplam := 0
	for _, s := range sonucListesi {
		toplam += s
	}

	fmt.Printf("toplam alinan is (tum iscilerin toplami): %d\n", len(sonucListesi))
	fmt.Printf("toplam rapor eden isci sayisi: %d\n", raporSayisi)
	fmt.Printf("hepsi ayni sebeple mi durdu (bekledigi geldi): %v\n", tumuGeldiMi)
	fmt.Printf("en az bos kalan isci sayisi (%d - %d): %d\n", isciSayisi, isSayisi, isciSayisi-isSayisi)
	fmt.Printf("toplanan sonuc sayisi: %d, toplam: %d\n", len(sonucListesi), toplam)
}
```

```
-- 5 is, 8 isci: kapatilan is kanali --
toplam alinan is (tum iscilerin toplami): 5
toplam rapor eden isci sayisi: 8
hepsi ayni sebeple mi durdu (bekledigi geldi): true
en az bos kalan isci sayisi (8 - 5): 3
toplanan sonuc sayisi: 5, toplam: 55
```

Ölçümün kendisi de sırayı çizelgeleyiciye bırakmıyor. `sonuclar` ve `raporlar` kanalları
`grup.Wait()` dönmeden **önce** kapatılmıyor — bekleme grubu (`sync.WaitGroup`) her işçinin
`grup.Done()` çağırdığı ana kadar `Wait()`i bloke ediyor ve sekiz işçinin sekizi de kendi
raporunu göndermeden `grup.Done()`e ulaşamıyor (yardımcı işlevin sonundaki satır sırası buna
bağlı). Bu sıralama olmasaydı, ana goroutine henüz hiçbir rapor ulaşmadan `raporlar` kanalını
kapatabilir ve `for r := range raporlar` döngüsü sekiz yerine daha az rapor görebilirdi —
koşumdan koşuma değişen bir sayı. `grup.Wait()`in burada yaptığı iş tam olarak bu: sekiz
işçinin **hepsinin** işini bitirdiğini, herhangi bir süre beklemeden, kanal üzerinden
garanti altına almak.

Kanalların tamponlu açılması da rastgele değil. `isler` tam `isSayisi` kapasitesiyle açılıyor,
böylece ana goroutine bütün işleri hiçbir işçi henüz başlamamışken bile gönderebiliyor — gönderim
işçilerin okumaya başlamasını beklemiyor. `raporlar` da `isciSayisi` kapasitesiyle açılıyor, çünkü
sekiz işçinin sekizi de raporunu göndermek isteyecek ve tamponsuz bir kanalda bu gönderim ana
goroutine'in okumasını beklerdi. Ana goroutine ise raporları ancak `grup.Wait()` döndükten sonra
okuyor; `grup.Done()` her işçide `defer`li olduğu için rapor gönderiminden **sonra** çalışıyor.
Tamponsuz bir `raporlar` kanalında ilk işçi gönderimde bloke olur, `Done`una hiç ulaşamaz, `Wait`
hiç dönmez ve program tümüyle kilitlenirdi. Tamponlar burada bir hız optimizasyonu değil, kodun
akışını basit tutan bir tasarım kararı.

**KL1.** Burada bilinçli bir seçim var: ölçüm hangi işçinin hangi işi aldığını hiç yazdırmıyor.
Sekiz işçi aynı kanaldan okumak için yarışıyor ve bu yarışın kazananı çalıştırmadan önce bilinemez
— bir koşumda üçüncü işçi iki iş alırken bir başka koşumda hiç almayabilir. **Toplam alınan iş**
(`sonuclar` kanalına yazılan kare sayısı) ve **toplam rapor eden işçi sayısı** (sekiz) ise hiçbir
zaman değişmiyor: birincisi kaç iş gönderildiğinin, ikincisi kaç işçi başlatıldığının doğrudan
sonucu, hangi işçinin ne aldığından bağımsız. Ders yalnız değişmeyeni ölçüyor.

**KL2.** "Hepsi aynı sebeple mi durdu" sorusunun yanıtı da aynı nedenle sabit: hangi işçi kaç
iş alırsa alsın, `isler` kanalı kapandığında **her işçinin** `for is := range isler` döngüsü
biter ve rapor gönderilir. Durma **sebebi** iş dağılımından bağımsız; yalnız durma **anı**
işçiden işçiye değişiyor ve bu ders zaten anı değil sebebi ölçüyor.

**KL3.** Ölçümün kendi başına neyi yazdırmadığı da kadar önemli: hiçbir satır "işçi 3, 2 iş aldı"
demiyor. Sebebi, bu bilginin bir koşumdan diğerine **aynı kalmayacak** olması — `isler` kanalını
sekiz goroutine aynı anda dinliyor ve hangisinin hangi göndermeyi yakalayacağı Go'nun çalışma
zamanı zamanlayıcısına bağlı. Bu bir kusur değil, paylaşılan bir kanalın doğal sonucu; ama
`kod_dogrula.py` çalıştırma çıktısını dersteki sabit beklenen-çıktı bloğuyla birebir
karşılaştırıyor, bu yüzden bu düzeydeki bir ayrıntıyı yazdırmak dersi kendi doğrulama aracına
karşı kırardı. Ölçüm bunun yerine yalnız koşumdan koşuma **değişmeyen** iki toplamı (alınan iş,
rapor eden işçi) yazdırıp üçüncü sayıyı (boş kalan işçi) bunlardan **türetiyor** — gözlemlenen
değil hesaplanan bir sayı. `isci` işlevinin aldığı `id` parametresi bu yüzden gövdede hiç
okunmuyor: kimliğin kendisi değil, kaç işçinin var olduğu ve hangi sınıfta durduğu ölçülüyor.

#### İşçi Sayısını Artırmak İşi Bölmez

**KL4.** Son satır kursun en net sınırlayıcı gözlemlerinden biri: sekiz işçi başlatıldı ama yalnız
beş iş vardı. `sonuclar` kanalına yazılan kare sayısı beşe eşit olduğuna göre — her işçi en az
sıfır iş almış olabilir ve toplam beş — **en fazla beş** işçi bir iş almış olabilir; kalan **en az
üç** işçi hiç iş almadan durmuş olmalı. Bu sonuç iki doğrudan ölçülen sayıdan (toplam alınan iş,
toplam işçi sayısı) çıkan bir **aritmetik zorunluluk**; hangi üç işçinin boş kaldığını
gözlemlemeye hiç gerek yok, çünkü kaçının boş kaldığı zaten toplamlardan belli. Aynı mantık her
`isSayisi < isciSayisi` durumunda geçerli: işçi sayısı sekizden dokuza çıkarılsaydı, boş kalan
işçi sayısının alt sınırı da üçten dörde çıkardı — işçi eklemek iş miktarını değiştirmediği
sürece, boş kalan işçi sayısının alt sınırı işçi sayısıyla **birlikte büyür**, onunla ters
orantılı değil.

Tersi durum da aynı aritmetikten çıkıyor: `isSayisi` `isciSayisi`den büyük olsaydı (örneğin
yirmi iş, sekiz işçi), boş kalan işçi sayısının alt sınırı sıfıra iner — sekiz işçinin sekizi
de en az bir iş almak zorunda kalır, çünkü yirmi işin hepsi bir yerde işlenmeli ve işleyecek
sekiz kanal ucu var. Boş kalma yalnız iş sayısı işçi sayısından **az** olduğunda ortaya çıkan
bir durum; havuzun kendisi bu iki yönü de aynı kod yoluyla, hiçbir özel durum eklemeden
karşılıyor.

İşçi sayısını artırmanın **var olan işi bölmediği** buradan görülüyor: iş kanalındaki beş iş,
kaç işçi dinlerse dinlesin, yalnız beş kez alınabiliyor. Sekizinci işçiyi eklemek beşinci işin
daha küçük parçalara ayrılmasını sağlamıyor — yalnız daha fazla işçinin sırada beklemesine
ve kanal kapandığında elleri boş durmasına yol açıyor. Bir işin kendisi bölünebilir bir birim
değilse (yukarıdaki `is * is` gibi, tek bir tam sayıyı işleyen atomik bir adım), işçi sayısını
iş sayısının üstüne çıkarmanın getirisi paralellik değil, yalnız boşta bekleyen işçi. Gerçek
bir sistemde bu genellikle zararsızdır — boşta bekleyen bir goroutine kaynak tüketmez, yalnız
kanalı dinler — ama havuzun büyüklüğünü iş yüküne göre ayarlamayı planlayan bir tasarımcı için
bu ölçüm şunu söylüyor: işçi sayısını artırmak, işin kendisi daha küçük parçalara **bölünebilir**
olmadıkça, hızlanmayı garanti etmiyor. Bu havuz düzeni burada yürütücü kavramıyla
karşılaştırılmıyor — **M07/K04 Eşzamanlılık ve Başarım**'ın havuz karşılaştırması burada
tekrarlanmıyor; ölçülen yalnız Go'nun kendi kanal tabanlı düzeneği.

Havuzun büyüklüğünü belirlemek bu yüzden işin **kendi yapısına** bakmayı gerektiriyor: iş tek
bir tam sayıyı karelemek gibi bölünemez bir adımsa, işçi sayısı iş sayısını geçtiği an fayda
düzleşiyor. İş daha büyük bir kümeyi (örneğin bir dilimin elemanlarını) taşıyorsa, aynı iş
kanalına o kümenin alt parçalarını ayrı ayrı göndermek işçi sayısını yeniden anlamlı kılıyor —
ama bu, işin kendisinin nasıl parçalandığına dair bir karar, havuzun kendisinin çözdüğü bir
şey değil.

#### Kapatılmayan Kuyruk: İşçiler Hiç Durmaz

Yukarıdaki ölçüm `isler` kanalını `close(isler)` ile kapatarak hepsinin durmasını sağladı.
Bu kapatma satırı unutulsaydı ne olurdu? Aşağıdaki ölçüm aynı düzeni kuruyor ama iş kanalını
hiç kapatmıyor.

```go
// sizinti.go — is kanali hic kapatilmazsa isciler hic durmaz; sizinti boolean olarak olculur
package main

import (
	"fmt"
	"runtime"
)

func isciSizinti(id int, isler <-chan int, sonuclar chan<- int) {
	for is := range isler {
		sonuclar <- is * is
	}
	// Bu satira hicbir zaman ulasilmiyor: isler kanali hic kapanmiyor.
}

func main() {
	taban := runtime.NumGoroutine()

	const isciSayisi = 4
	isler := make(chan int) // kapatilmiyor
	sonuclar := make(chan int, isciSayisi)

	for id := 1; id <= isciSayisi; id++ {
		go isciSizinti(id, isler, sonuclar)
	}

	fmt.Println("-- is kanali hic kapatilmadi --")
	fmt.Printf("baslatilan isci sayisi: %d\n", isciSayisi)
	fmt.Printf("durmayan isci kaldi mi: %v\n", runtime.NumGoroutine() > taban)
}
```

```
-- is kanali hic kapatilmadi --
baslatilan isci sayisi: 4
durmayan isci kaldi mi: true
```

**KL5.** Dört işçi başlatıldı ve hiçbiri hiç bir iş almadı — çünkü `isler` kanalına hiçbir
değer gönderilmedi. Ama asıl gözlem bu değil: dört işçinin dördü de `for is := range isler`
satırında **sonsuza kadar** bekliyor, çünkü kanal ne bir değer taşıyor ne kapanıyor. Bu, önceki
derste kurulan "kendi durdu da bir sözleşmedir" gözleminin havuz düzenindeki karşılığı: bir
işçinin durması, kendi gövdesinin bir kararı değil, **iş kanalını yöneten tarafın** bir kararı.
`runtime.NumGoroutine()`'ın taban sayının üstünde kalması bunun tek kanıtı — program hiçbir
hata, hiçbir uyarı vermeden normal çıkışla sonlanıyor, dört goroutine arkada asılı kalmış
olsa bile. Kaç goroutine kaldığı burada basılmıyor, yalnız kaldığının kendisi.

Bu ölçümün doğruluğu koşum zamanlamasına bağlı değil: `go` deyimi çalıştığı anda yeni goroutine
çalışma zamanına **kaydediliyor**, henüz hiçbir kod çalıştırmamış olsa bile; bu yüzden döngü
bitip `runtime.NumGoroutine()` çağrıldığında dört işçinin dördü de sayıma zaten girmiş oluyor.
Ölçüm bu yüzden dört işçinin fiilen bloklanmasını beklemeye gerek duymuyor — kaydın kendisi
yeterli, çünkü mesele işçilerin şu an ne yaptığı değil, **var olup olmadıkları**.

Bu tek sızıntı kısa ömürlü bir program için gözlemlenebilir bir soruna yol açmayabilir —
dört goroutine, sonlanan bir sürecin belleğiyle birlikte zaten temizlenir. Asıl risk deseni
**tekrarlamakta**: bir istek işleyicisi her çağrıldığında kendi havuzunu kurup iş kanalını
kapatmayı unutuyorsa, sızan işçi sayısı gelen isteklerle birlikte büyür. Bu ders tek bir
sızıntıyı ölçüyor, çoğaltılmış bir sızıntıyı değil — ama boolean'ın kendisi zaten yeterli
uyarı: `true` yanıtı, kanalın hiçbir zaman kapanmayacağı bir tasarımın işareti, kaç goroutine
biriktiği önemli değil.

#### Özet

- Sabit sayıda işçi paylaşılan bir iş kanalından okur; kanal kapandığında hepsi **aynı
  sebeple** durur (beklediği geldi), hangi işi kimin aldığından bağımsız olarak.
- Toplam alınan iş ve toplam rapor eden işçi sayısı, iş dağılımından bağımsız iki sabit
  değerdir; bu ikisinin farkından, en az kaç işçinin hiç iş almadığı hesaplanır.
- İşçi sayısını iş sayısının üstüne çıkarmak var olan işi bölmez; yalnız boş kalan işçi
  sayısını artırır, çünkü her iş bölünemez bir birim olarak yalnız bir kez alınır.
- İş kanalı kapatılmazsa işçiler sonsuza kadar bekler; sızıntının kanıtı yalnız bir boolean
  olarak ölçülür; kaç goroutine kaldığı basılmaz.
- Kapatma sorumluluğu burada da gönderende durur — işçi havuzunda gönderen, işi dağıtan
  taraftır.

#### Sonraki Adım

Bu derste bütün işçiler **aynı kanaldan** okudu ve kanalın kapanması hepsine aynı anda
ulaştı. Ama her zaman tek bir kaynak yeterli olmuyor: bazen bir iş, tek bir üreticinin
üretebileceğinden daha hızlı tüketilmesi gereken bir akıştan geliyor ve o akışın kendisi
birden fazla tüketiciye **yayılmak** zorunda. Sıradaki ders bu yayılmayı ölçüyor — bir kaynağın
çok sayıda tüketiciye dağılması ve sonuçların tek bir kanalda yeniden toplanması — ve bu sefer
sorulan soru işçi havuzundakinden farklı: toplama kanalını kapatma sorumluluğu birden fazla
üreticiden hangisinde duruyor?

### Yayma ve Toplama

Source: https://academia.sh/tr/kurslar/go-eszamanlilik/yayma-ve-toplama

Bir kaynak, sabit sayıda tüketiciye yayıldığında (yayma) sonuçlar tek bir toplama kanalında yeniden birleşiyor (toplama); toplayıcının durma sebebi, bir bekleme grubunun bütün üreticiler bitince toplama kanalını kapatmasına bağlı. Sınırlayıcı ölçüm: toplama kanalını kim kapatır sorusunun tek yanıtı vardır — hiçbir üretici tek başına güvenle kapatamaz (deneyen üç üreticiden ikisi paniklerken biri başarır) ve kapatılmazsa toplayıcı hiç durmaz.

Önceki ders, sabit sayıda işçinin **aynı** iş kanalını dinlediği bir kurulumu ölçtü: tek kaynak,
çok tüketici ve durma sinyali hepsine tek bir kapatmayla ulaşıyordu. Ama iş her zaman
işçilerin kendi başına yetiştirebileceği hızda gelmiyor. Bazen üretilen değerlerin kendisi
birden fazla işlemciye **dağıtılmalı** — bu **yayma** — ve o dağıtılmış işin sonuçları tekrar
tek bir akışta **toplanmalı** — bu **toplama**. Önceki dersin
sorduğu "kanalı kim kapatır" sorusu burada da geçerli, ama yanıtı farklı bir yerde duruyor:
işçi havuzunda kapatan taraf işi **dağıtan** tarafken, burada kapatılması gereken kanala
**birden fazla** taraf yazıyor — ve bu, "kim kapatır" sorusunu ilk kez gerçek bir soruna
çeviriyor.

Yayma ve toplama iki ayrı yönü kapsıyor. Yayma, bir üretim kaynağının (bir kuyruk, bir kanal)
birden fazla işlemciye açılması — önceki dersteki iş kanalıyla neredeyse aynı düzenek, çünkü
paylaşılan bir kanaldan okuyan sabit sayıda goroutine zaten bir yaymadır. Toplama ise tersi
yönde çalışıyor: birden fazla kaynağın **tek** bir kanalda birleşmesi. İki yön birlikte
kurulduğunda ortaya yeni bir soru çıkıyor — yaymada kapatan taraf bellidir (kaynağı yöneten
tek taraf), ama toplamada kapatılması gereken kanala kaç tarafın yazdığı, kapatma sorumluluğunu
da o kadar taraf arasında paylaştırma riski taşıyor.

#### Bir Kaynağın Çoğa Yayılması, Sonuçların Tek Kanalda Toplanması

Aşağıdaki ölçüm altı değeri üç üretici goroutine'ine yayıyor; her üretici kendi payına düşen
değerleri kareleyip tek bir `toplama` kanalına yazıyor. Toplama kanalını kimin kapatacağı
burada zaten çözülmüş: ayrı bir goroutine, bir bekleme grubunun (`sync.WaitGroup`) bütün
üreticilerin bittiğini bildirmesini bekliyor ve ancak o zaman `toplama`yı kapatıyor. Tek
sahiplik burada **bir üreticide değil, üreticilerin dışında** kuruluyor.

```go
// yaymatoplama.go — kaynak uc uretene yayiliyor, sonuclar tek kanalda toplaniyor; toplama kanalini bekleme grubu kapatiyor
package main

import (
	"fmt"
	"sort"
	"sync"
)

func uretici(girdi <-chan int, toplama chan<- int, grup *sync.WaitGroup) {
	defer grup.Done()
	for deger := range girdi {
		toplama <- deger * deger
	}
}

func main() {
	const isSayisi = 6
	const ureticiSayisi = 3

	girdi := make(chan int, isSayisi)
	toplama := make(chan int, isSayisi)
	var grup sync.WaitGroup

	for id := 1; id <= ureticiSayisi; id++ {
		grup.Add(1)
		go uretici(girdi, toplama, &grup)
	}

	for i := 1; i <= isSayisi; i++ {
		girdi <- i
	}
	close(girdi)

	// Toplayici: butun ureticiler bitince toplama kanalini KENDISI kapatiyor;
	// hicbir uretici kendi basina kapatmiyor.
	go func() {
		grup.Wait()
		close(toplama)
	}()

	fmt.Println("-- kaynak 3 uretene yayiliyor, sonuclar tek kanalda toplaniyor --")
	sonuclar := make([]int, 0, isSayisi)
	for s := range toplama {
		sonuclar = append(sonuclar, s)
	}
	sort.Ints(sonuclar)

	toplamDeger := 0
	for _, s := range sonuclar {
		toplamDeger += s
	}
	fmt.Printf("yayilan is sayisi: %d\n", isSayisi)
	fmt.Printf("toplanan sonuc sayisi: %d, toplam: %d\n", len(sonuclar), toplamDeger)
	fmt.Println("toplayicinin durma sebebi: bekledigi geldi (toplama kanali kapandi)")
}
```

```
-- kaynak 3 uretene yayiliyor, sonuclar tek kanalda toplaniyor --
yayilan is sayisi: 6
toplanan sonuc sayisi: 6, toplam: 91
toplayicinin durma sebebi: bekledigi geldi (toplama kanali kapandi)
```

**KL6.** `girdi` kanalı önceki dersteki `isler` ile aynı rolü oynuyor — üç üretici arasında
paylaşılan bir kuyruk. Farklı olan `toplama` kanalı: üç üretici de **aynı anda** ona yazıyor,
tek bir işçi değil. Sonuç sayısının altıya, toplamın doksan bire (kareler `1+4+9+16+25+36`)
çıkması hangi üreticinin hangi değeri işlediğinden bağımsız; sıralama (`sort.Ints`) da bu
yüzden gerekiyor — üç üreticinin toplama kanalına yazma sırası koşumdan koşuma değişebilir,
ama kümenin kendisi değişmiyor.

Bu ölçümde `uretici` işlevi, önceki dersteki `isci` işleviyle neredeyse aynı gövdeye sahip:
ikisi de bir girdi kanalından okuyor, bir çıktı kanalına yazıyor. Aradaki fark işlevin kendi
kodunda değil, çağrıldığı bağlamda — `isci` sabit sayıda tüketiciyi aynı işi paylaşacak
biçimde kurarken, `uretici` aynı işlevi bu kez **birden fazla yazıcının aynı hedefe yazması**
bağlamında çalıştırıyor. Kalıbın adı değişiyor (işçi havuzundan yaymaya), ama alttaki kanal
düzeneği aynı kalıyor; değişen yalnız kapatma sorumluluğunun nerede durduğu.

**KL7.** Kapatan goroutine burada üçüncü bir taraf: ne üreticilerden biri, ne toplayıcının
kendisi. `grup.Wait()` üç üreticinin üçü de `grup.Done()`e ulaşana kadar dönmüyor ve ancak
o zaman `close(toplama)` çalışıyor — bu, toplama kanalına yapılacak **son** yazmanın zaten
gerçekleşmiş olduğunu garanti ediyor. `girdi` ve `toplama` kanallarının ikisi de `isSayisi`
kapasitesiyle tamponlu açılıyor, aynı önceki dersteki gerekçeyle: üreticiler kendi paylarını
alıp yazarken engellenmeden çalışabilsin diye. Bu üçüncü taraf yaklaşımı bir sonraki bölümün
sorusunu doğuruyor: ya bu ayrı goroutine hiç olmasaydı ve üreticilerin her biri kendi işini
bitirince kanalı kendisi kapatmaya çalışsaydı?

#### Toplama Kanalını Kim Kapatır

Bir kanalı kapatmak Go'da **tek seferlik** bir işlem: aynı kanalı iki kez kapatmak panikler.
Üç üretici de aynı kanalı paylaşıyorsa ve her biri kendi işini bitirince "ben bittim, kanalı
kapatayım" derse, ilk kapatan başarılı olur ama ondan sonraki her kapatma denemesi panikler.
Aşağıdaki ölçüm bunu kuruyor: her üretici önce kendi değerini gönderiyor, sonra (bir bekleme
noktasıyla bütün üreticilerin göndermeyi bitirdiğinden emin olunduktan sonra) kendi başına
`close(toplama)` deniyor.

```go
// naifkapatma.go — her uretici kendi basina kapatmayi denerse, ilkinden sonrakiler panikler
package main

import (
	"fmt"
	"sync"
)

func ureticiNaif(id int, toplama chan int, senkron *sync.WaitGroup, sonuc chan<- bool, bitis *sync.WaitGroup) {
	defer bitis.Done()
	toplama <- id
	senkron.Done()
	senkron.Wait() // butun ureticiler kendi degerini gonderene kadar bekle
	panikledi := false
	func() {
		defer func() {
			if r := recover(); r != nil {
				panikledi = true
			}
		}()
		close(toplama)
	}()
	sonuc <- panikledi
}

func main() {
	const ureticiSayisi = 3
	toplama := make(chan int, ureticiSayisi)
	sonuc := make(chan bool, ureticiSayisi)
	var senkron sync.WaitGroup
	var bitis sync.WaitGroup
	senkron.Add(ureticiSayisi)
	bitis.Add(ureticiSayisi)

	for id := 1; id <= ureticiSayisi; id++ {
		go ureticiNaif(id, toplama, &senkron, sonuc, &bitis)
	}
	bitis.Wait()
	close(sonuc)

	basarili, panikleyen := 0, 0
	for panikledi := range sonuc {
		if panikledi {
			panikleyen++
		} else {
			basarili++
		}
	}
	fmt.Println("-- her uretici once kendi degerini gonderiyor, sonra kapatmayi deniyor --")
	fmt.Printf("basariyla kapatan uretici sayisi: %d / %d\n", basarili, ureticiSayisi)
	fmt.Printf("panikleyen uretici sayisi: %d / %d\n", panikleyen, ureticiSayisi)
}
```

```
-- her uretici once kendi degerini gonderiyor, sonra kapatmayi deniyor --
basariyla kapatan uretici sayisi: 1 / 3
panikleyen uretici sayisi: 2 / 3
```

**KL8.** Sonuç şaşırtıcı olmayan bir biçimde tek yanıtlı: her koşumda tam olarak **bir**
üretici başarıyla kapatıyor, kalan **ikisi** panikliyor. Hangi üreticinin başardığı koşumdan
koşuma değişebilir (üç üretici de kapatmayı aynı anda denemeye başlıyor), ama **kaç**
üreticinin başardığı hiç değişmiyor — Go'nun çalışma zamanı bir kanalın kapatılmasını iç
kilitle koruyor, bu yüzden birden fazla goroutine aynı anda `close` çağırsa bile yalnız biri
kanalı gerçekten kapatıyor, geri kalanı zaten kapanmış bir kanalı kapatmaya çalıştığını görüp
panikliyor. Ders bu yüzden **kimin** başardığını değil, **kaçının** başardığını yazdırıyor.

Bu iç kilit, `close`in kendisinin **atomik** bir işlem olduğu anlamına geliyor: bir kanalın
"kapalı mı değil mi" durumu, birden fazla goroutine aynı anda sorup değiştirmeye çalışsa bile,
her zaman tek bir sıralı geçmişe sahip. Üç goroutine `close(toplama)`yı gerçekten aynı anda
çağırsa da çalışma zamanı bunları görünmez bir sırayla işliyor — ilk işlenen kapatma
gerçekleşiyor, ondan sonraki her biri zaten kapalı bir kanalı bulup panikliyor. Bu davranış
paylaşılan bir değişkene korumasız yazmaktan çok farklı: paylaşılan bir sayaca aynı anda iki
goroutine yazarsa sonuç tanımsız bir yarış koşuludur (ölçülmüyor, yalnız kavram olarak
**M01/K05 İşletim Sistemi Kavramları**'nda kuruldu), ama kanal kapatma dilin kendisinin garanti
ettiği, yarışa açık olmayan bir işlem — kazanan kim olursa olsun tam olarak bir kazanan
çıkıyor.

**KL9.** İkinci senkronizasyon noktası (`senkron`) burada bilinçli bir tasarım kararı:
ölçüm her üreticinin **önce kendi değerini gönderdiğinden**, sonra kapatmayı denediğinden emin
oluyor. Bu ayrım olmasaydı, bir üretici henüz kendi değerini göndermeden bir başkası kanalı
kapatabilir ve o üreticinin gönderimi (`toplama <- id`) **kapatma satırından önce** değil
**panik** olarak sonuçlanırdı — bu da hangi hattın hangi sebeple paniklediğini karıştırırdı.
`senkron.Wait()`, önceki derste görülen `grup.Wait()` gibi, sırayı çizelgeleyiciye bırakmadan
iki aşamayı (gönderme, kapatma) birbirinden ayırıyor.

Bu ayrımın kendisi bir gözlemi gizliyor: bir kapalı kanalla ilgili panik aslında **iki** ayrı
biçimde tetiklenebiliyor — kapalı bir kanalı tekrar kapatmak (`close of closed channel`) ve kapalı
bir kanala göndermek (`send on closed channel`). Yukarıdaki `senkron` engeli olmasaydı, bir
üretici henüz kendi değerini göndermeden (`toplama <- id`) bir başkası kanalı kapatmış olabilirdi
ve o gönderim satırı kendi `recover` bloğunun **dışında** kaldığı için panik yakalanmadan program
çöker; ders `kod_dogrula.py`ye hiç ulaşmadan durur. `senkron.Wait()` engeli tam olarak bunu
önlüyor: bütün gönderimlerin bittiğinden emin olunmadan hiçbir üretici kapatmayı denemiyor, bu
yüzden gözlenen panik türü hep aynı (kapalı kanalı tekrar kapatma), hiçbir zaman diğeri değil.

Bu ölçüm önceki bölümdeki doğru düzenin **neden** doğru olduğunu gösteriyor: toplama kanalını
kapatma sorumluluğu üreticilerin **hiçbirine** ait değil, çünkü hiçbiri diğerlerinin ne zaman
biteceğini bilmiyor. Sorumluluk, ancak bütün üreticilerin bittiğini bilen üçüncü bir tarafa
(bir bekleme grubunu bekleyen ayrı bir goroutine'e) verilebiliyor.

#### Kapatılmayan Toplama Kanalı

Önceki iki kurulum, kapatma sorumluluğunun **sıfır** ile **birden fazla** taraf arasında
paylaştırılmasının iki farklı sonucunu gösterdi: birinci kurulumda sorumluluk tam olarak bir
tarafa aitti ve her şey sessizce bitti; ikinci kurulumda sorumluluk (yanlışlıkla) üç tarafa
aitti ve iki panik ortaya çıktı. Geriye üçüncü olasılık kalıyor: sorumluluk **hiç kimseye**
verilmezse ne olur? Üçüncü taraf goroutine (yukarıdaki bekleme grubunu bekleyip toplama
kanalını kapatan ayrı goroutine) hiç kurulmazsa, toplama kanalı hiç kapanmıyor. Aşağıdaki
ölçüm bunu en yalın
haliyle kuruyor: bir toplayıcı goroutine `toplama` kanalında aralık yapıyor, ama kanalı
kapatacak hiç kimse yok.

```go
// sizintitoplama.go — toplama kanali hic kapatilmazsa toplayici hic durmaz
package main

import (
	"fmt"
	"runtime"
)

func main() {
	taban := runtime.NumGoroutine()

	toplama := make(chan int)
	go func() {
		// Kimse toplama kanalini kapatmadigi icin bu aralik hic bitmeyecek.
		for range toplama {
		}
	}()

	fmt.Println("-- toplama kanali hic kapatilmadi --")
	fmt.Printf("durmayan toplayici kaldi mi: %v\n", runtime.NumGoroutine() > taban)
}
```

```
-- toplama kanali hic kapatilmadi --
durmayan toplayici kaldi mi: true
```

**KL10.** Bu, önceki dersteki kapatılmayan iş kanalı ölçümünün toplama tarafındaki aynası:
orada işçiler sonsuza kadar bekliyordu, burada tek bir toplayıcı sonsuza kadar bekliyor. Fark,
sızıntının **yerinde**: işçi havuzunda sızan taraf çok sayıda tüketiciydi, burada sızan taraf
tek bir toplayıcı — ama ikisinde de sebep aynı, kimsenin kapatmadığı bir kanalda aralık.
Toplayıcının kendisi de bir goroutine olduğu için, onun sızması da işçi sızıntısından farklı
bir araçla değil, aynı `runtime.NumGoroutine()` karşılaştırmasıyla ölçülüyor.

Bu üç kurulumun ortak noktası, hiçbirinin sorunu **kaynağında** çözmemesi: doğru kurulum bile
bütün üreticilerin **sonunda** biteceğini varsayıyor. Üreticilerden biri kendi işini hiç
bitirmezse (örneğin sonsuz bir döngüye girerse), `grup.Wait()` de hiç dönmez ve toplama kanalı
yine hiç kapanmaz — bu kurulum kapatma sorumluluğunu doğru bir tarafa veriyor, ama o tarafın
kendisi de bir koşula bağlı kalıyor. Bir üreticinin kendi işini bitirmemesi olasılığını
**sınırlamak** (belirli bir süre ya da dışarıdan gelen bir sinyalle) bu dersin konusu değil;
o soruyu bağlam paketiyle iptal yayma ölçen bir sonraki ders ele alıyor.

Üç ölçüm birlikte konunun tezini tamamlıyor: doğru kurulan toplama (bir kişide toplanan
sorumluluk) belirlenimci ve sessiz durdu; yanlış kurulan toplama (herkesin kendi başına
kapatmaya çalışması) belirlenimci ama **gürültülü** durdu — paniklerle; ve hiç kurulmayan
toplama (kimsenin kapatmaması) hiç durmadı. Üçü de aynı sorunun farklı yanıtları: toplama
kanalını kapatma sorumluluğu tam olarak **bir** tarafa ait olmalı, ne az ne çok. Bu ilke işçi
havuzundaki tek kapatan tarafla (işi dağıtan taraf) aynı ilkenin bir genellemesi: kaç taraf
bir kanala yazarsa yazsın, kapatma her zaman ayrı ve tekil bir sorumluluk olarak kalıyor.

#### Özet

- Bir kaynak sabit sayıda üreticiye yayıldığında (yayma), sonuçlar tek bir toplama kanalında
  yeniden birleşiyor (toplama); toplanan sonuç sayısı ve toplamı üretici sayısından bağımsız
  iki sabit değer.
- Toplama kanalını kapatma sorumluluğu üreticilerin hiçbirine ait değil — bir bekleme grubunun
  bütün üreticilerin bittiğini bildirdiği üçüncü bir taraf kapatıyor.
- Her üretici kendi başına kapatmayı denerse, tam olarak bir tanesi başarır, kalanı panikler;
  hangi üreticinin başardığı değişse de kaçının başardığı hiç değişmiyor.
- Toplama kanalı hiç kapatılmazsa toplayıcı sonsuza kadar bekler; sızıntı yine yalnız bir
  boolean olarak ölçülüyor.
- Kapatma sorumluluğunun tam olarak bir tarafa ait olması gerekiyor: sıfır taraf sızıntıya,
  birden fazla taraf paniğe yol açıyor.
- Kanal kapatma dilin kendisinin garanti ettiği atomik bir işlem; birden fazla goroutine aynı
  anda `close` çağırsa bile tam olarak biri başarır, geri kalanı panikler.

#### Sonraki Adım

Bu derste bir kaynak çoğa yayıldı ve sonuçlar tek bir kanalda toplandı — ama akış tek bir
adımdı: her üretici girdiyi aldı, kareledi, sonucu yazdı. Gerçek bir işleme genellikle birden
fazla adımdan geçiyor ve her adımın kendi hızı, kendi kapatma sorumluluğu var. Sıradaki ders bu
adımları zincirliyor — aşamalı akış bir boru hattı kuruyor ve her aşama kendi çıktı kanalını
kendisi kapatıyor. Soru bu sefer tek bir kapatmanın kimde durduğu değil: zincirdeki bir aşama
erken durursa, önündeki aşamalara ne oluyor?

### Boru Hatları

Source: https://academia.sh/tr/kurslar/go-eszamanlilik/boru-hatlari

Aşamalı akış (boru hattı) düzeninde her aşama kendi çıktı kanalını kendisi açar ve kendisi kapatır; bir üretim, bir kare alma ve bir toplama aşamasından geçen beş değer, hiçbir aşamanın sırayı bozmadığı bir zincirde akıyor. Sınırlayıcı ölçüm: tüketici zincirin ortasından erken vazgeçtiğinde önündeki aşamalar hiç durmaz — kapatma sorumluluğu her aşamada doğru kurulsa bile, zincirin en ucundaki tüketici vazgeçtiğinde hiçbiri bunu bilmiyor. Çözüm sıradaki dersin konusu.

Önceki ders kapatma sorumluluğunun kaç tarafa ait olması gerektiğini ölçtü: sıfır taraf
sızıntıya, birden fazla taraf paniğe yol açıyordu, doğrusu tam olarak bir taraftı. Ama o
üretim tek bir adımdan ibaretti — girdi alınır, karelenir, sonuç yazılır. Gerçek bir işleme
genellikle birden fazla adımdan geçiyor ve her adımın kendi hızı, kendi çıktısı var. Bu ders bu
adımları zincirliyor: **aşamalı akış**, yani boru hattı (pipeline) — her aşama önceki aşamanın
çıktısını kendi girdisi olarak alan, kendi çıktısını üreten ve **kendi çıktı kanalını kendisi
kapatan** bir zincir. Kapatma sorumluluğu artık tek bir yerde değil, zincirin her halkasında
ayrı ayrı duruyor.

Önceki iki dersteki düzenlerle karşılaştırıldığında aşamalı akışın şekli farklı. İşçi
havuzunda çok sayıda goroutine **aynı** kanaldan okuyordu (paylaşılan bir kuyruk); yaymada çok
sayıda goroutine **aynı** kanala yazıyordu (paylaşılan bir hedef). Aşamalı akışta ise her kanal
tam olarak bir yazıcı ve bir okuyucu arasında duruyor — paylaşım yok, yalnız zincirleme.
Bu, önceki iki dersteki en zor sorunun (kapatmayı kaç tarafın üstlenmesi gerektiği) burada hiç
ortaya çıkmadığı anlamına geliyor: her kanalın tam olarak bir sahibi var, o da kanalı açan
aşamanın kendisi. Zincirin kendi iç yapısı belirlenimciliği bedavaya getiriyor; asıl soru bu
dersin ikinci bölümünde ortaya çıkıyor — zincirin **dışından**, tüketiciden gelen bir
vazgeçmeyle.

#### Aşamalı Akış: Her Aşama Kendi Kapatma Sorumluluğunu Taşır

Aşağıdaki ölçüm üç aşamalı bir hat kuruyor: `uret` bir sayı aralığı üretiyor, `kareAl` her
değeri kareliyor, `birEkle` her değere bir ekliyor. Üçü de aynı biçimde yazılmış: bir kanal
açıyor, kendi işini yapan bir goroutine başlatıyor, o goroutine işini bitirince **kendi
kanalını kendisi kapatıyor** ve kanalın kendisini çağıranına döndürüyor.

```go
// asamalar.go — uc asamali boru hatti: her asama kendi ciktisini kendisi acar ve kendisi kapatir
package main

import "fmt"

// uret, 1..n araligindaki degerleri kendi ciktisina yazar ve isini bitirince kapatir.
func uret(n int) <-chan int {
	cikti := make(chan int)
	go func() {
		defer close(cikti)
		for i := 1; i <= n; i++ {
			cikti <- i
		}
	}()
	return cikti
}

// kareAl her degeri karesiyle degistirir; girdi kapaninca kendi ciktisini kapatir.
func kareAl(girdi <-chan int) <-chan int {
	cikti := make(chan int)
	go func() {
		defer close(cikti)
		for deger := range girdi {
			cikti <- deger * deger
		}
	}()
	return cikti
}

// birEkle her degere bir ekler; girdi kapaninca kendi ciktisini kapatir.
func birEkle(girdi <-chan int) <-chan int {
	cikti := make(chan int)
	go func() {
		defer close(cikti)
		for deger := range girdi {
			cikti <- deger + 1
		}
	}()
	return cikti
}

func main() {
	asama1 := uret(5)
	asama2 := kareAl(asama1)
	asama3 := birEkle(asama2)

	fmt.Println("-- uc asamali hat, tam tuketim --")
	sonuclar := make([]int, 0, 5)
	for s := range asama3 {
		sonuclar = append(sonuclar, s)
	}
	fmt.Printf("akan deger sayisi: %d\n", len(sonuclar))
	fmt.Println("degerler:", sonuclar)
	fmt.Println("her asamanin durma sebebi: bekledigi geldi (bir onceki asamanin kanali kapandi)")
}
```

```
-- uc asamali hat, tam tuketim --
akan deger sayisi: 5
degerler: [2 5 10 17 26]
her asamanin durma sebebi: bekledigi geldi (bir onceki asamanin kanali kapandi)
```

**KL11.** Beş değer ( `1..5` ) sırasıyla kareleniyor (`1,4,9,16,25`) ve her birine bir
ekleniyor (`2,5,10,17,26`) — sonuç listesi bu sırayla basılıyor ve bu sıra **hiçbir yerde
sıralanmadan** ortaya çıkıyor. Sebep önceki iki dersteki yayma/toplama düzenlerinden farklı:
orada birden fazla üretici **aynı** kanala aynı anda yazıyordu, bu yüzden sıra kaybolabiliyordu
ve toplanan sonuçların sıralanması gerekiyordu. Burada her kanalın **tek** bir yazıcısı ve
**tek** bir okuyucusu var (`uret`in tek goroutine'i, `kareAl`in tek goroutine'i, ana
goroutine'in kendisi); tek yazıcı/tek okuyucu ilişkisinde bir kanal gönderme sırasını her
zaman koruyor, çünkü yarışacak ikinci bir taraf yok.

Ana goroutine'in kendisi de zincirin bir parçası: `asama3`ün tek okuyucusu o ve tüketim
döngüsü `birEkle`in goroutine'i kendi çıktısını kapatana kadar sürüyor. Zincirin
başından sonuna kadar toplam dört goroutine çalışıyor — `uret`, `kareAl`, `birEkle`in üçü ve
ana goroutine'in kendisi — ama hiçbiri diğerini beklemek için özel bir senkronizasyon aracına
(bir bekleme grubuna, bir `select`e) ihtiyaç duymuyor; kanalların kendisi hem veri taşıyor hem
sırayı dayatıyor.

**KL12.** Her aşamanın durma sebebi de aynı: `kareAl` ve `birEkle`, kendi girdi kanalları
kapandığında (`for deger := range girdi` biterken) kendi çıktı kanallarını kapatıyor —
`defer close(cikti)` bunu garanti ediyor, girdi tüketilirken panik olsa bile. `uret`in kendi
goroutine'i ise beş değeri gönderdikten sonra döngüsü bitince aynı deferle kapanıyor. Zincirin
her halkası, önceki derste kurulan "kapatma sorumluluğu tam olarak bir tarafa aittir" ilkesini
**kendi** kanalı için ayrı ayrı uyguluyor — zincirdeki üç kanalın üçü de kendi üreticisi
tarafından kapatılıyor, hiçbiri paylaşılmıyor.

Bu düzenin ölçeklenmesi de aynı ilkeye dayanıyor: zincire dördüncü, beşinci bir aşama eklense,
her yeni aşama yalnız bir önceki aşamanın çıktısını girdi olarak alıp kendi kanalını açar —
`asama4 := birBaskaDonusum(asama3)` gibi bir çağrı, önceki üç aşamanın hiçbirine dokunmadan
zincire eklenebilir. Belirlenimcilik zincirin uzunluğundan bağımsız, çünkü her ek halka de
aynı tek-yazıcı/tek-okuyucu kısıtına uyuyor; üç aşamalı bir hatta doğru olan (sıranın
korunması, kapatmanın kendiliğinden yayılması) on aşamalı bir hatta da doğru kalıyor.

`boru hattı` terimi burada bir niteleyiciyle kullanılıyor: bu, **aşamalı akış boru hattı** —
kabuk programlamadaki komut boru hattından ya da sürekli teslimat hattındaki adımlardan ayrı
bir kavram, yalnız adı aynı. Ortak olan şey, her ikisinde de bir çıktının bir sonraki adımın
girdisi olması; farklı olan, buradaki "aşama"nın bir kabuk süreci ya da bir dağıtım basamağı
değil, bir kanal çifti etrafında kurulmuş bir goroutine olması.

Her aşama işlevinin dönüş tipi de bunu yansıtıyor: `uret`, `kareAl` ve `birEkle` üçü de
`<-chan int` döndürüyor — yalnız **alınabilen**, gönderilemeyen bir kanal. Bu, çağıranın
yanlışlıkla döndürülen kanala kendi değerini göndermesini derleme zamanında engelliyor;
kanalın **tek yazıcısı** onu oluşturan işlevin kendi goroutine'i ve dönüş tipi bu tekliği
tipe kodluyor. Aynı kısıtlama girdi parametrelerinde de var: `kareAl` ve `birEkle`,
`girdi <-chan int` alıyor — yalnız kendi kanallarından okuyabilir, oraya yazamazlar. Bu küçük
tip ayrıntısı, "her kanalın tek bir sahibi var" ilkesini yalnız bir tasarım niyeti olarak değil,
derleyicinin denetlediği bir kural olarak kuruyor.

`kareAl` ve `birEkle`in gövdeleri kelimesi kelimesine aynı kalıba uyuyor: bir çıktı kanalı aç,
bir goroutine başlat, girdi kanalında aralık yap, her değeri dönüştürüp çıktıya yaz, girdi
biterken kendi çıktını kapat. Bu tekrar tesadüf değil — herhangi bir dönüştürücü aşama aynı
üç adımı izliyor, değişen yalnız aradaki dönüşüm (`deger * deger` ya da `deger + 1`). `uret`
bu kalıbın bir ucu (girdi kanalı yok, yalnız üretim) ve ana goroutine'deki tüketim döngüsü
öbür ucu (çıktı kanalı yok, yalnız tüketim); aradaki her aşama ikisinin ortasında, hem bir
girdisi hem bir çıktısı olan aynı şablonu tekrarlıyor. Bir hatta kaç aşama olursa olsun, kapatma
sorumluluğu her zaman bu şablonun içinde, `defer close(cikti)` satırında duruyor.

#### Erken Bırakma: Önceki Aşamalar Hiç Durmaz

Yukarıdaki ölçüm zincirin **sonuna kadar** tüketti — `asama3`teki bütün değerler alınana kadar
döngü sürdü. Ama bir tüketici her zaman zincirin sonuna kadar gitmeyebilir: aradığı ilk sonucu
bulduğunda durabilir, bir hata alabilir, ya da başka bir sebeple vazgeçebilir — örneğin bir
arama işlemi ilk eşleşmeyi bulduğunda kalan değerleri hiç istemeyebilir. Aşağıdaki ölçüm bu
durumu en yalın haliyle kuruyor: iki aşamalı bir hat açılıyor ve tüketici ikinci değerden sonra
vazgeçiyor, `for range` döngüsünden `break` ile çıkıyor.

```go
// erkenbirak.go — tuketici erken vazgecerse onceki asamalar hic durmaz (cozum sonraki derste)
package main

import (
	"fmt"
	"runtime"
)

func uret(n int) <-chan int {
	cikti := make(chan int)
	go func() {
		defer close(cikti)
		for i := 1; i <= n; i++ {
			cikti <- i
		}
	}()
	return cikti
}

func kareAl(girdi <-chan int) <-chan int {
	cikti := make(chan int)
	go func() {
		defer close(cikti)
		for deger := range girdi {
			cikti <- deger * deger
		}
	}()
	return cikti
}

func main() {
	taban := runtime.NumGoroutine()

	asama1 := uret(5)
	asama2 := kareAl(asama1)

	alinan := 0
	for range asama2 {
		alinan++
		if alinan == 2 {
			break // tuketici iki degerden sonra vazgeciyor
		}
	}

	fmt.Println("-- tuketici 2 degerden sonra vazgeciyor --")
	fmt.Printf("alinan deger sayisi: %d\n", alinan)
	fmt.Printf("durmayan asama kaldi mi: %v\n", runtime.NumGoroutine() > taban)
}
```

```
-- tuketici 2 degerden sonra vazgeciyor --
alinan deger sayisi: 2
durmayan asama kaldi mi: true
```

**KL13.** İki değer alındıktan sonra `break` ile döngüden çıkılıyor — ve bu, zincirin geri
kalanına hiç haber verilmiyor. Zincirde tam olarak nerede takılma olduğu zamanlamaya bağlı, ama
olası uçları saymak öğretici: `kareAl`in goroutine'i ya kendi kareleme sonucunu `asama2`ye
göndermeye çalışırken takılıyor — çünkü ana goroutine artık onu almıyor — ya da bir sonraki değeri
`asama1`den almaya çalışırken bekliyor. Eğer `kareAl` gönderirken takılıysa, `uret`in goroutine'i
de bir sonraki değerini `asama1`e göndermeye çalıştığında takılıyor, çünkü `kareAl` artık onu
okuyamayacak kadar meşgul. Zincir kısa olduğu için iki olası durum da aynı sonuca varıyor: **iki**
goroutine (`uret` ve `kareAl`) kalıcı olarak bloke oluyor. Tüketici tam iki değer aldığı için
`kareAl` üçüncü gönderiminde, `uret` de dördüncüsünde takılı kalıyor; hangi sırayla takıldıkları
zamanlamaya bağlı olsa da takılanların sayısı değişmiyor — bu yüzden ölçüm yalnız
`runtime.NumGoroutine()` karşılaştırmasını, kaç goroutine'in kaldığını değil, yazdırıyor.

**KL14.** Bu, önceki iki derste görülen kapatılmayan kanal sızıntılarından **farklı bir
sebeple** ortaya çıkıyor. Orada sorun kapatmanın hiç yapılmamasıydı; burada her aşama kendi
kapatma sorumluluğunu **doğru** taşıyor — `uret` de, `kareAl` da kendi çıktısını işi bitince
kapatacak. Sorun, işin hiç **bitmemesi**: `uret`in "bitmesi" beşinci değeri göndermesine bağlı,
ama üçüncü değerden sonra kimse almıyorsa gönderim hiç tamamlanmıyor, dolayısıyla `defer close`
satırına da hiç ulaşılmıyor. Doğru kurulmuş bir kapatma sorumluluğu, zincirin **her**
halkasının birbirini beklediği varsayımına dayanıyor; bu varsayım tüketici erken çıktığında
bozuluyor ve hiçbir aşama bunu fark edecek bir mekanizmaya sahip değil.

Bu, önceki dersteki "doğru kurulum bile üreticilerin sonunda biteceğini varsayıyor" gözleminin
zincire yayılmış hâli. Orada tek bir varsayım vardı (üreticiler bitecek); burada zincirdeki
**her** aşama bir öncekinin ve bir sonrakinin normal çalıştığını varsayıyor ve bu varsayımların
zincirleme etkisi zincir uzadıkça büyüyor. Üç aşamalı bir hatta bir tüketicinin erken
çıkması iki goroutine'i etkileyebiliyorken, on aşamalı bir hatta aynı erken çıkış dokuz
goroutine'i bloke edebilir — sızıntının büyüklüğü zincirin uzunluğuyla birlikte artıyor,
tüketicinin ne kadar erken vazgeçtiğinden bağımsız olarak.

Üç dersin sızıntı ölçümlerini yan yana koymak farkı netleştiriyor. İşçi havuzunda sızıntının
sebebi kapatmanın **unutulmasıydı** — `close(isler)` satırı hiç yazılmamıştı. Yaymada sızıntının
sebebi sorumluluğun **hiç kimseye verilmemesiydi** — üçüncü taraf goroutine hiç kurulmamıştı.
Burada sorumluluk hem yazılmış hem doğru tarafa verilmiş, ama tetiklenmesi gereken **olay**
(girdinin tükenmesi) hiç gerçekleşmiyor, çünkü girdiyi tüketecek olan zaten aradan çekilmiş.
Üçü de aynı boolean ölçütle (`runtime.NumGoroutine() > taban`) yakalanıyor, ama üçünün kök
sebebi üç ayrı yerde duruyor: eksik kapatma, sahipsiz kapatma, tetiklenmeyen kapatma.

Bu sınır, ders içinde **çözülmüyor** — yalnız ölçülüyor. Bir aşamayı "tüketici artık
dinlemiyor" bilgisiyle donatmak, o aşamanın kendi döngüsünün dışından gelen bir sinyali
denetlemesini gerektiriyor; her aşamanın `for deger := range girdi` döngüsüne ek bir dal
eklemesi ve bu dalın **bütün** aşamalara aynı anda ulaşan tek bir kaynaktan gelmesi
gerekiyor. Bu tam olarak sıradaki dersin konusu.

#### Özet

- Aşamalı akışta (boru hattı) her aşama kendi çıktı kanalını kendisi açar ve kendisi kapatır;
  kapatma sorumluluğu tek bir yerde değil, zincirin her halkasında ayrı ayrı durur.
- Zincirdeki her kanalın tek bir yazıcısı ve tek bir okuyucusu olduğu için sıra hiçbir
  sıralama aracına gerek kalmadan korunur — yayma/toplamadaki paylaşılan kanallardan farklı.
- `boru hattı` terimi burada aşamalı akış anlamında, kabuk ya da teslimat hattı boru
  hatlarından ayrı bir kavram olarak kullanılıyor.
- Tüketici zincirden erken çıktığında önündeki aşamalar bunu hiç bilmiyor ve kalıcı olarak
  bloke oluyor; sızıntı yine yalnız bir boolean olarak ölçülüyor.
- Bu sızıntının kaynağı, önceki derslerdeki kapatılmayan kanallardan farklı: kapatma
  sorumluluğu doğru kurulu, ama işin kendisi hiç bitmiyor.
- Sızıntının büyüklüğü zincirin uzunluğuyla birlikte artıyor: bir tüketicinin erken çıkışı
  kısa bir hatta bir goroutine'i, uzun bir hatta zincirin geri kalanını etkileyebilir.

#### Sonraki Adım

Bu derste görülen sızıntının çözümü, bir aşamanın yalnız kendi girdi kanalını değil,
**dışarıdan gelen bir vazgeçme sinyalini** de dinlemesini gerektiriyor — ve bu sinyalin
zincirin başından sonuna kadar bütün aşamalara **aynı anda** ulaşması gerekiyor, tek tek
her aşamaya elle iletilmeden. Sıradaki ders bu sinyali taşıyan aracı ölçüyor: bir iptalin,
zincirdeki bütün bekleyenlere nasıl aynı anda ulaştığını ve bir goroutine'in bu sinyali hiç
denetlemediğinde ne olduğunu.

### Bağlam Paketi

Source: https://academia.sh/tr/kurslar/go-eszamanlilik/baglam-paketi

Tek bir iptal, ctx.Done() üzerinde bekleyen dört goroutine'in dördüne de aynı anda ulaşıyor; hepsi 'iptal' sınıfında duruyor ve ctx.Err() aynı sabit sebebi taşıyor. İstek kapsamlı bir değer (context.WithValue) taşımanın ne için olduğu ve ne için olmadığı ayrı ayrı ölçülüyor. Sınırlayıcı ölçüm: bağlam kimseyi zorla durdurmuyor, yalnız haber veriyor — ctx.Done()'u hiç denetlemeyen bir goroutine, bağlam zaten iptal edilmiş olsa bile kendi işini sonuna kadar tamamlıyor.

Önceki ders bir sızıntıyı ölçüp çözümsüz bıraktı: bir boru hattında tüketici erken çıktığında,
önündeki aşamaların bunu bilecek hiçbir yolu yoktu. Aşamaların kendi girdi kanallarını
dinlemesi yetmiyordu — dışarıdan gelen, bütün aşamalara **aynı anda** ulaşan bir sinyale
ihtiyaç vardı. Bu ders o sinyali taşıyan aracı ölçüyor: bağlam paketi (`context` paketi). Bir
bağlamın iptal edilmesi, onu bekleyen kaç goroutine olursa olsun, hepsine tek bir olayla
ulaşıyor — kanaldan farklı olarak burada "kaç alıcı" sorusu hiç yok, çünkü bir bağlamın iptal
sinyali **yayın** gibi çalışıyor, tek bir alıcıya değil hepsine birden.

Bağlam paketinin ortaya çıkardığı `context.Context` tipi kendi başına yeni bir düzenek değil —
altında yatan yalnız bir kanal (`Done()`in döndürdüğü) ve bir hata değeri (`Err()`in
döndürdüğü). Yeni olan, bu ikilinin bir **ağaç** biçiminde türetilebilmesi: bir bağlamdan yeni
bir bağlam türetildiğinde (`context.WithCancel`, `context.WithTimeout` gibi çağrılarla),
üstteki bağlam iptal edildiğinde bütün alt bağlamlar da otomatik olarak iptal ediliyor. Bu ders
tek katmanlı bir ağaçla (`context.Background()`den türeyen tek bir bağlam) başlıyor; ağacın
kendisi bu dersin konusu değil, ama tek bir düğümün iptalinin kaç bekleyene ulaştığı burada net
biçimde ölçülüyor.

#### Tek Bir İptal, Zincirdeki Bütün Bekleyenler

Aşağıdaki ölçüm dört goroutine başlatıyor; her biri `ctx.Done()` üzerinde bekliyor. Tek bir
`iptalEt()` çağrısından sonra dördü de raporunu gönderiyor — hepsi aynı sınıfta: **iptal**.

```go
// iptalyayilir.go — tek bir iptal, baglami bekleyen butun goroutine'lere ayni anda ulasir
package main

import (
	"context"
	"fmt"
	"sort"
	"sync"
)

type rapor struct {
	id    int
	sebep string
}

func bekleyen(id int, ctx context.Context, raporlar chan<- rapor, grup *sync.WaitGroup) {
	defer grup.Done()
	<-ctx.Done()
	raporlar <- rapor{id: id, sebep: "iptal"}
}

func main() {
	const bekleyenSayisi = 4
	ctx, iptalEt := context.WithCancel(context.Background())

	raporlar := make(chan rapor, bekleyenSayisi)
	var grup sync.WaitGroup
	for id := 1; id <= bekleyenSayisi; id++ {
		grup.Add(1)
		go bekleyen(id, ctx, raporlar, &grup)
	}

	iptalEt()
	grup.Wait()
	close(raporlar)

	kayitlar := make([]rapor, 0, bekleyenSayisi)
	for r := range raporlar {
		kayitlar = append(kayitlar, r)
	}
	sort.Slice(kayitlar, func(i, j int) bool { return kayitlar[i].id < kayitlar[j].id })

	fmt.Println("-- tek iptal, dort bekleyen --")
	for _, r := range kayitlar {
		fmt.Printf("bekleyen %d  sebep=%s\n", r.id, r.sebep)
	}
	fmt.Println("ctx.Err():", ctx.Err())
}
```

```
-- tek iptal, dort bekleyen --
bekleyen 1  sebep=iptal
bekleyen 2  sebep=iptal
bekleyen 3  sebep=iptal
bekleyen 4  sebep=iptal
ctx.Err(): context canceled
```

**KL15.** `context.WithCancel`in döndürdüğü `ctx.Done()` bir **kanal**, ama sıradan bir kanal
değil: `iptalEt()` çağrıldığında bu kanal **kapanıyor** ve önceki derslerde defalarca görülen
"kapanan kanaldan alım bloklanmaz" davranışı burada da geçerli. Dört goroutine'in dördü de
`<-ctx.Done()` satırında bekliyordu; kanal kapandığı an dördü de aynı anda serbest kalıyor.
Kapatma önceki derslerde hep **tek bir tarafa** aitti — bir üretici, bir aşama, ya da Yayma ve
Toplama dersindeki gibi bütün üreticilerin bittiğini bilen üçüncü bir taraf; burada da öyle.
`iptalEt`in kendisi tek bir kapatma çağrısını sarmalıyor; birden fazla `iptalEt` çağrısı bile
kanalı yalnız bir kez kapatıyor. Bu, Yayma ve Toplama dersindeki "kanal kapatma atomik bir
işlemdir" gözleminin bağlam paketindeki karşılığı. Bu da bilinçli bir tasarım kararı: `iptalEt`i
birden fazla kez, hatta birden fazla goroutine'den çağırmak güvenli — çağıranın "acaba daha önce
iptal ettim mi" diye kendi durumunu takip etmesi gerekmiyor, çünkü ikinci ve sonraki çağrılar
sessizce hiçbir şey yapmıyor.

Dört raporun `id`ye göre sıralanması (`sort.Slice`), önceki derslerdeki toplama kanallarında
görülen aynı ihtiyaçtan geliyor: dört goroutine `raporlar` kanalına aynı anda yazabiliyor,
kanal kapandığında hangisinin önce ulaştığı zamanlamaya bağlı. Fark, bu kez sıralamanın
**sonucu** etkilememesi — dördünün de sınıfı zaten aynı (iptal), sıralama yalnız çıktının
okunabilir ve tekrarlanabilir olmasını sağlıyor.

**KL16.** Dördünün de aynı sınıfta (**iptal**) durması, kursun ilk dersinde kurulan dört sınıflı
ölçütün (kendi, beklediği geldi, iptal, hiç durmadı) bu bağlamdaki en yalın hâli: hiçbiri kendi
gövdesini bitirmedi, hiçbiri başka bir goroutine'in bir adımını bekliyordu, dördü de tek bir iptal
olayının doğrudan sonucu olarak durdu. `ctx.Err()`in döndürdüğü metin de sabit — "context
canceled" — dilin kendi standart kütüphanesinin tanımladığı, sürüme bağlı olmayan bir sabit dize;
hangi bağlamın hangi sebeple iptal edildiğini ayırt etmek isteyen bir çağıran bu metni
okuyabiliyor.

Bu dördü, önceki derslerdeki "iptal" sınıfının **tek** örneğiydi (ortak tanımın iki bekleyeni)
— burada aynı sınıf dört kata çıkarılıyor ve sonuç değişmiyor, çünkü kanalın kapanması kaç
alıcı olursa olsun aynı davranışı gösteriyor. Bu ölçek bağımsızlığı bağlam paketinin asıl
gücü: bir web sunucusunda tek bir isteğin iptali, o isteği işleyen onlarca goroutine'e (bir
veritabanı sorgusu, birkaç dış çağrı, bir önbellek okuması) aynı `ctx.Done()` kanalı
üzerinden, hiçbirini tek tek uyarmaya gerek kalmadan ulaşabiliyor.

#### İstek Kapsamlı Değer Taşımak

Bağlam paketinin ikinci işi iptal yaymak değil, bir **istek kapsamlı değeri** çağrı zinciri
boyunca taşımak — örneğin bir istek kimliğini, zincirdeki her işlevin imzasını değiştirmeden,
en derindeki çağrıya kadar ulaştırmak. Bu, önceki derslerdeki hiçbir kanal düzeneğiyle
karşılanmayan ayrı bir ihtiyaç: kanallar veri **akıtır**, bağlam değerleri ise çağrı zincirinin
**yanında** taşır. İşçi havuzu, yayma/toplama ve boru hattı ölçümlerinin üçünde de veri hep
açık bir parametre ya da açık bir kanal üzerinden aktı; `context.WithValue`in çözdüğü sorun
farklı — bir istek kimliğini, o isteği işleyen zincirdeki her işlevin imzasına ayrı ayrı
eklemeden, zincirin en derinine kadar ulaştırmak.

Değer taşıma, iptal yaymadan bağımsız çalışıyor: aşağıdaki ölçüm önce bir değer taşıma
kurulumunu, sonra (aynı programda, ayrı bir bağlamla) iptalin kimseyi durdurmadığını gösteren
sınırlayıcı ölçümü kuruyor.

```go
// degerVeIhmal.go — istek kapsamli deger tasima ve iptali hic denetlemeyen goroutine
package main

import (
	"context"
	"fmt"
)

type anahtarTipi string

const istekAnahtari anahtarTipi = "istek-kimligi"

func iclerdeOku(ctx context.Context) string {
	deger, varMi := ctx.Value(istekAnahtari).(string)
	if !varMi {
		return "yok"
	}
	return deger
}

func main() {
	ctx := context.WithValue(context.Background(), istekAnahtari, "istek-42")
	fmt.Println("iclerdeki islevin okudugu istek kimligi:", iclerdeOku(ctx))

	ctx2, iptalEt := context.WithCancel(context.Background())
	iptalEt() // bu goroutine baslamadan once iptal zaten gerceklesti

	adimlar := make(chan int, 3)
	go func() {
		// ctx2.Done() hic denetlenmiyor; bu goroutine iptali hic bilmiyor.
		for adim := 1; adim <= 3; adim++ {
			adimlar <- adim
		}
		close(adimlar)
	}()

	tamamlananAdim := 0
	for range adimlar {
		tamamlananAdim++
	}
	fmt.Println("goroutine calisirken baglam zaten iptal edilmis miydi:", ctx2.Err() != nil)
	fmt.Printf("tamamlanan adim sayisi: %d / 3\n", tamamlananAdim)
}
```

```
iclerdeki islevin okudugu istek kimligi: istek-42
goroutine calisirken baglam zaten iptal edilmis miydi: true
tamamlanan adim sayisi: 3 / 3
```

**KL17.** `istekAnahtari`, sıradan bir dizgi değil, kendi tanımlı `anahtarTipi` türünde bir
sabit — bu, bir başka paketin aynı dizgi değerini kendi anahtarı olarak kullanıp yanlışlıkla
aynı bağlam yuvasına yazmasını derleme zamanında engelliyor. `iclerdeOku`, `ctx.Value`in
döndürdüğü değeri **tip iddiasıyla** (`.(string)`) okuyor ve ikinci dönüş değeriyle (`varMi`)
değerin hiç bulunmama ihtimalini ayırıyor — sözlükteki iki yanıtlı erişimin (**M09/K01**'den)
bağlam değerlerindeki karşılığı.

Bu taşıma biçiminin **ne olmadığı** da ölçünün bir parçası: `context.WithValue`, bir işlevin
ihtiyaç duyduğu **gerekli** bir bağımlılığı ya da parametreyi taşımak için değil, bir çağrı
zincirinin **her** katmanından geçmesi gereken, isteğe özgü ve o katmanların çoğunun
kullanmadığı üstbilgi türünden veriler (bir istek kimliği, bir izleme belirteci) için var.
`iclerdeOku` işlevi, istek kimliğini bulamazsa (`varMi` `false` ise) çökmüyor, yalnız "yok"
döndürüyor — çünkü bağlam değeri **isteğe bağlı** kabul ediliyor. Bir işlevin çalışması için
zorunlu olan bir değer (bir veritabanı bağlantısı, bir yapılandırma) bağlam üzerinden değil,
doğrudan bir parametre olarak geçirilmeli; aksi hâlde işlevin imzası neye ihtiyaç duyduğunu
söylemekten vazgeçmiş olur.

Bu ayrımın pratik sonucu, `iclerdeOku` gibi bir işlevin imzasına bakarak görülemiyor: `func
iclerdeOku(ctx context.Context) string` imzası, işlevin bir istek kimliğine ihtiyaç duyduğunu hiç
söylemiyor — yalnız bir bağlam alıyor ve bağlamın içinde ne olduğu çağrı zamanına kadar belirsiz
kalıyor. Bu, Go Temelleri kursundaki `nil` sözlük ve `nil` işaretçi davranışlarına benzer bir
açıklık: tip sistemi, bağlamın **hangi** anahtarları taşıdığını denetlemiyor, yalnız bir anahtar
sorulduğunda `nil`/`varMi=false` ile yokluğu bildiriyor. Bir işlevin hangi bağlam anahtarlarını
okuduğunu bilmenin tek yolu kaynağı okumak — imza bu konuda hiçbir söz vermiyor.

#### Bağlam Kimseyi Durdurmaz, Yalnız Haber Verir

İkinci ölçüm bunu doğrudan kanıtlıyor. `ctx2` goroutine başlamadan **önce** zaten iptal edilmiş
— `iptalEt()` çağrısı, `go func() {...}()` satırından önce çalışıyor. Bu sıralama bilinçli:
ölçümün amacı, "iptal henüz gerçekleşmemişken goroutine başladı, sonra iptal yetişemedi" gibi
bir zamanlama tartışmasını tamamen ortadan kaldırmak. Bağlam, goroutine daha var olmadan
zaten iptal edilmiş durumda; buna rağmen başlatılan goroutine `ctx2.Done()`u hiç denetlemiyor,
yalnız üç adımı sırayla üretip kanalını kapatıyor.

**KL18.** Sonuç net: `ctx2.Err() != nil` `true` — goroutine çalışmaya başladığında bağlam
**zaten** iptal edilmişti — ama `tamamlanan adim sayisi` yine de `3 / 3`. Goroutine, bağlamın
iptal edilip edilmediğine hiç bakmadığı için, iptalin kendisinden **tamamen habersiz**
çalışmaya devam etti ve işini eksiksiz bitirdi. Bu, önceki dersin kapanışında bırakılan sorunun
tam yanıtı: bağlam paketi kimseyi zorla durdurmuyor, bir goroutine'i **kesip atan** bir
mekanizma değil. Yaptığı tek şey, iptal olduğunda bunu isteyen herkese haber vermek —
`ctx.Done()`u dinleyenler bu haberi alıp kendi kararlarını veriyor, dinlemeyenler hiçbir şey
fark etmiyor.

Bu davranış ilk bakışta bir kusur gibi görünebilir — neden bağlam kendisi bir goroutine'i
durdurmuyor? Yanıt Go'nun genel tasarımında yatıyor: dilin çalışma zamanı, bir goroutine'i
dışarıdan **zorla** durdurma aracı hiç sunmuyor (bir iş parçacığını dışarıdan kesmeye benzer
bir çağrı Go'da yok). Bir goroutine yalnız kendi isteğiyle, kendi kod yolunda bir `return`e ya
da fonksiyonun sonuna ulaşarak durabiliyor. Bağlam paketi bu kısıtın içinde çalışıyor: zorla
durdurma sunmadığı için, elindeki tek araç **bilgilendirme** — durması gerektiğini düşünen
goroutine'e bunu söylemek, kararı ona bırakmak. Bu, önceki derslerde defalarca görülen "durma
kararı her zaman goroutine'in kendi gövdesinde bir kontrol noktasına bağlıdır" ilkesinin en
açık örneği: kontrol noktası burada `select` içindeki bir `case <-ctx.Done()` dalı ve o dal
kaynakta hiç yazılmamışsa iptalin kendisi de bir anlam ifade etmiyor.

Bu, önceki derste ölçülen boru hattı sızıntısının çözümünün de sınırını çiziyor: bir aşamaya
`ctx.Done()`u denetleyen bir `select` dalı eklemek, o aşamayı **iptali fark eden** hâle
getiriyor — ama bu ekleme her aşamada ayrı ayrı yapılmak zorunda. Örneğin `kareAl` aşaması,
kendi çıktısına düz bir gönderim yerine iki dallı bir `select` yazsaydı — biri değeri gönderen,
öbürü `ctx.Done()`u bekleyen — tüketici erken çıktığında bağlamı iptal ederek bu aşamayı da
serbest bırakabilirdi. Ama bu değişikliği zincirdeki **her** aşama ayrı ayrı yapmak zorunda;
bağlamın kendisi hiçbir goroutine'i otomatik olarak durdurmuyor, yayılan şey yalnız **bilgi**,
bu bilgiyi eyleme çevirmek her goroutine'in kendi sorumluluğunda kalıyor.

İptal bağlamının bu doğası, bir `context.Context` değerinin neden bir kilit ya da bir kesme
(interrupt) gibi çalışmadığını açıklıyor: o yalnız kapanan bir kanal ve kapanan bir kanalı
kim dinliyorsa yalnız o tepki veriyor. Bu da bağlam paketini önceki derslerdeki bütün
düzeneklerle aynı temele oturtuyor — kanal, bekleme grubu, `select`, hepsi aynı ilkeye
dayanıyor: eşzamanlılık Go'da bir goroutine'i dışarıdan yönetmekle değil, goroutine'lerin
birbirine **haber verdiği** bir kanal ağıyla kuruluyor.

#### Özet

- Bir bağlamın iptal edilmesi, `ctx.Done()` üzerinde bekleyen kaç goroutine olursa olsun,
  hepsine aynı anda ulaşıyor; hepsi aynı sınıfta (iptal) duruyor.
- `ctx.Err()`, dilin standart kütüphanesinin tanımladığı sabit bir metin döndürüyor; hangi
  bağlamın iptal edildiğini ayırt etmek için okunabiliyor.
- `context.WithValue`, isteğe bağlı, katmanlar arasında taşınan üstbilgi türünden veriler
  içindir; bir işlevin zorunlu bağımlılıklarının yerini tutmaz.
- Bağlam kimseyi zorla durdurmuyor, yalnız haber veriyor — `ctx.Done()`u denetlemeyen bir
  goroutine, bağlam iptal edilmiş olsa bile işini sonuna kadar sürdürüyor.
- Bir goroutine'i iptale duyarlı hâle getirmek, o goroutine'in kendi döngüsüne bir `ctx.Done()`
  denetimi eklemesini gerektiriyor; bu otomatik gerçekleşmiyor.
- Go'nun çalışma zamanı bir goroutine'i dışarıdan zorla durdurma aracı sunmuyor; bağlam bu
  kısıt içinde çalışıyor ve elindeki tek araç bilgilendirme, zorlama değil.

#### Sonraki Adım

Bu derste bağlamın **kendisinin** ne zaman iptal edildiği önceden biliniyordu — `iptalEt()`
açıkça çağrıldı, bekleyenler bu çağrıyı bekledi. Ama bir bağlamın iptal edilmesinin en yaygın
sebebi, birinin bilinçli bir karar vermesi değil, **bir sürenin dolması**: bir ağ çağrısının,
bir veritabanı sorgusunun makul bir süre içinde yanıt vermesini beklemek, vermezse vazgeçmek.
Sıradaki ders bu özel durumu ölçüyor: bir `select` deyimi bir sonucu mu yoksa bir iptali mi
aldığına göre bir dal seçtiğinde, zaman aşımı da bu seçimin bir biçimi oluyor — ölçülen şey
geçen süre değil, hangi dalın seçildiği; bu ders zaten bilinen bir iptali ölçtü, sıradaki ders
**henüz gerçekleşmemiş** bir iptali (bir zaman aşımını) aynı `select` düzeneğiyle ölçüyor.

### Zaman Aşımı ve Son Tarihler

Source: https://academia.sh/tr/kurslar/go-eszamanlilik/zaman-asimi-ve-son-tarihler

Zaman aşımı bir select deyiminin bir dal seçimidir: sonuç önceden hazırsa sonuç dalı, bağlam önceden iptal edilmişse (zaman aşımını temsilen) iptal dalı seçiliyor — ikisi de belirlenimci, çünkü kurulumda yalnız bir dal gerçekten hazır. Süre hiçbir yerde ölçülmüyor, basılan tek şey hangi dalın seçildiği. Sınırlayıcı ölçüm: zaman aşımı bekleyen tarafı kurtarıyor ama beklenen işi durdurmuyor — dalından çıkan çağıranın arkasında, sonucunu artık kimsenin almayacağı bir iş goroutine'i sızıntı olarak kalıyor.

Önceki ders bir bağlamın iptalinin, o iptal **zaten gerçekleştikten sonra** bekleyenlere nasıl
ulaştığını ölçtü — `iptalEt()` açıkça çağrılmıştı, bekleyenler bu çağrıyı bekliyordu. Ama
gerçek sistemlerde iptalin en sık sebebi bir kişinin kararı değil, bir **sürenin dolması**: bir
ağ çağrısının makul bir sürede yanıt vermesini beklemek, vermezse vazgeçmek. Bu ders zaman
aşımını ölçüyor — ama süre olarak değil. Zaman aşımı, aslında bir `select` deyiminin iki
dalından birinin seçilmesinden ibaret: sonuç mu geldi, iptal mi geldi. Bu ders süreyi hiç
ölçmeden, hangi dalın seçildiğini **belirlenimci** biçimde kuruyor.

Bağlam paketi bunun için iki ayrı kurucu sunuyor: `context.WithTimeout`, şu andan itibaren
**göreli** bir süre sonra iptal eden bir bağlam kurar; `context.WithDeadline` ise takvimde
**mutlak** bir an (bir son tarih) belirtir ve o ana ulaşıldığında iptal eder. İkisi de aynı
sonuca varıyor — belirli bir noktada kendiliğinden kapanan bir `Done()` kanalı — ve `select`
açısından ikisi de önceki dersteki `context.WithCancel`den hiçbir şekilde ayırt edilemiyor.
Bu ders bu yüzden ikisini de ayrı ayrı kurmuyor; ikisinin de vardığı ortak nokta olan "bağlam
bir noktada kapanmış" durumunu modelliyor ve gerçek bir süre ya da gerçek bir takvim değeri
hiç yazdırmıyor.

#### Bir Dal Seçimi: Sonuç mu, İptal mi

Aşağıdaki ölçüm iki kurulumu yan yana koyuyor. Birincisinde sonuç **önceden hazır**: `select`
çalıştığında sonuç kanalında zaten bir değer bekliyor, bağlam iptal edilmemiş. İkincisinde
bağlam **önceden iptal edilmiş** — zaman aşımını temsilen — ve sonuç kanalı boş, hiçbir
gönderici yok. Her iki kurulumda da `select`in hangi dalı seçeceği önceden bellidir, çünkü
kurulumun kendisinde yalnız **bir** dal gerçekten hazır.

```go
// dalSecimi.go — zaman asimi bir dal secimidir; sure degil hangi dalin secildigi olculur
package main

import (
	"context"
	"fmt"
)

func dalSec(ctx context.Context, sonuc <-chan string) string {
	select {
	case deger := <-sonuc:
		return "sonuc: " + deger
	case <-ctx.Done():
		return "iptal: " + ctx.Err().Error()
	}
}

func main() {
	fmt.Println("-- kurulum 1: sonuc onceden hazir --")
	ctxHazir, iptalHazir := context.WithCancel(context.Background())
	defer iptalHazir()
	sonucHazir := make(chan string, 1)
	sonucHazir <- "tamamlandi"
	fmt.Println("secilen dal:", dalSec(ctxHazir, sonucHazir))

	fmt.Println()
	fmt.Println("-- kurulum 2: baglam onceden iptal edilmis (zaman asimini temsil eder) --")
	ctxIptal, iptalEt := context.WithCancel(context.Background())
	iptalEt()
	sonucBos := make(chan string)
	fmt.Println("secilen dal:", dalSec(ctxIptal, sonucBos))
}
```

```
-- kurulum 1: sonuc onceden hazir --
secilen dal: sonuc: tamamlandi

-- kurulum 2: baglam onceden iptal edilmis (zaman asimini temsil eder) --
secilen dal: iptal: context canceled
```

**KL19.** Her iki kurulumda da `dalSec` çağrıldığı anda `select`in önünde yalnız **bir** hazır
dal var — bu, sonucun rastgele seçilmediğinin garantisi. Kurulum 1'de `sonucHazir` tamponlu
bir kanal ve içine değer `select` çalışmadan **önce** konmuş; `ctxHazir` iptal edilmemiş,
yani `ctx.Done()` hiç kapanmamış. `select`, iki daldan yalnız birinin hazır olduğu bir durumda
her zaman o dalı seçiyor — burada seçim şansa değil, kurulumun kendisine bağlı. Kurulum 2'nin
aynası: `ctxIptal` `dalSec` çağrılmadan önce iptal edilmiş, `sonucBos` ise tamamen boş ve
hiçbir goroutine ona yazmıyor — `ctx.Done()` dalı burada tek hazır dal.

Bu iki kurulum bilerek uç noktalara yerleştirildi: gerçek bir yarışta iki dal da **aynı anda**
hazır hâle gelebilir (sonuç tam zaman aşımıyla aynı ana denk gelirse) ve o durumda `select`
hangisini seçeceğine rastgele karar veriyor — bu, `select` Deyimi dersinde kurulan aynı
rastgelelik kuralı. Ama bu ders o ortak noktayı hiç kurmuyor: iki kurulumun ikisi de yalnız bir
dalın hazır olduğu, öbürünün kesinlikle hazır olmadığı durumlar. Belirlenimcilik buradan geliyor —
ölçüm rastgeleliğin **olmadığı** iki köşeyi seçiyor, rastgeleliğin olabileceği orta bölgeyi hiç
koşturmuyor.

**KL20.** Gerçek bir zaman aşımı `context.WithTimeout` ile kurulur ve bağlam belirli bir süre
sonra **kendiliğinden** iptal olur — bu dersin ölçtüğü kurulumlar o gerçek zamanlamayı
**modelliyor**, gerçek bir süre geçmesini hiç beklemeden. Kurulum 2'deki `ctxIptal`in "zaten
iptal edilmiş" olması, "süresi dolmuş bir zaman aşımı bağlamı" ile aynı sonucu üretiyor:
`select`in görünen yüzü ikisinde de aynı, çünkü `select` bir bağlamın **neden** iptal
edildiğini bilmiyor, yalnız `Done()` kanalının kapalı olup olmadığını görüyor. Bu, ölçümün
gerçek bir zaman aşımı kadar geçerli olmasını sağlıyor — fark yalnız iptalin **ne zaman**
tetiklendiğinde, `select`in davranışında değil.

Süre burada hiçbir satırda geçmiyor: ne bir `time.Sleep`, ne bir `time.After`, ne bir ölçülen
gecikme. Ölçülen tek şey **hangi dalın seçildiği** — bu, önceki dersteki "süre değil, seçilen
dal yazılır" kuralının doğrudan uygulanışı. `time.Sleep` ile bir sıra kurmanın neden yasak
olduğu burada bir kez daha görünüyor: eğer bu ölçüm gerçek bir zaman aşımı süresiyle
kurulsaydı, hangi dalın kazanacağı makinenin o anki yüküne bağlı kalırdı — modelleme bu
belirsizliği tamamen ortadan kaldırıyor.

`dalSec` işlevinin kendisi hem kurulum 1'de hem kurulum 2'de **aynı** — hiçbir dallanma,
hiçbir özel durum kodu yok. Bu, zaman aşımı düzeninin en pratik yanı: bir işlevi zaman aşımına
duyarlı yapmak, o işlevin içine "eğer süre dolduysa" türünden bir kontrol eklemeyi
gerektirmiyor; yalnız bir `select` içine iptal dalını eklemek yetiyor, gerisini kapatma
sorumluluğu zaten kurulmuş olan bağlam kendisi hallediyor. Aynı `dalSec`, çağırana
`WithCancel`den gelen bir iptal ile de, `WithTimeout`dan gelen bir zaman aşımıyla da, hiçbir
değişiklik gerekmeden hizmet ediyor.

#### Zaman Aşımı Bekleyeni Kurtarır, Beklenen İşi Durdurmaz

Yukarıdaki `dalSec` çağrısı döndüğünde, çağıran taraf **kurtulmuş** oluyor — artık bekleme
yok, bir sonuç ya da bir iptal metniyle devam edebiliyor. Ama bu kurtuluş, sonucu üretmesi
beklenen işin de durduğu anlamına gelmiyor. Aşağıdaki ölçüm bunu kurulum 2'nin üzerine inşa
ediyor: çağıran taraf iptal dalından çıktıktan **sonra**, aslında sonucu üretmekte olan bir iş
goroutine'i başlıyor ve sonucunu göndermeye çalışıyor — ama artık onu alacak kimse yok.

```go
// zamanasimiSizinti.go — zaman asimi bekleyeni kurtarir ama beklenen isi durdurmaz
package main

import (
	"context"
	"fmt"
	"runtime"
)

func dalSec(ctx context.Context, sonuc <-chan string) string {
	select {
	case deger := <-sonuc:
		return "sonuc: " + deger
	case <-ctx.Done():
		return "iptal: " + ctx.Err().Error()
	}
}

func main() {
	ctx, iptalEt := context.WithCancel(context.Background())
	iptalEt() // zaman asimini temsil eder: cagiran zaten vazgecmis durumda

	sonuc := make(chan string) // tamponsuz, henuz hicbir gonderici yok

	fmt.Println("-- cagiran dalindan cikiyor --")
	fmt.Println("secilen dal:", dalSec(ctx, sonuc))

	// Cagiran cikti. Is aslinda hala suruyormus gibi modellemek icin is goroutine'i
	// simdi baslatiliyor ve sonucunu ayni kanala yazmaya calisiyor. Baglami hic
	// denetlemedigi icin devam ediyor, ama artik alici yok.
	taban := runtime.NumGoroutine()
	go func() {
		sonuc <- "gec kalan sonuc"
	}()

	fmt.Printf("is goroutine'i hala calisiyor mu (sizinti): %v\n", runtime.NumGoroutine() > taban)
}
```

```
-- cagiran dalindan cikiyor --
secilen dal: iptal: context canceled
is goroutine'i hala calisiyor mu (sizinti): true
```

**KL21.** `dalSec` çağrıldığında `sonuc` kanalına henüz hiçbir goroutine yazmıyor — bu yüzden
seçilen dal kesin biçimde `ctx.Done()`. İş goroutine'i ancak `dalSec` döndükten **sonra**
başlıyor; bu sıralama bilinçli, çünkü amaç iş goroutine'inin `select` sırasında var olup
olmamasını bir zamanlama sorusu olmaktan çıkarmak. İş goroutine'i başladığında kendi sonucunu
`sonuc` kanalına göndermeye çalışıyor, ama kanal tamponsuz ve artık onu okuyan hiç kimse yok —
`dalSec` çoktan döndü, ana goroutine artık `sonuc`u dinlemiyor. Gönderim kalıcı olarak
bloke oluyor.

**KL22.** `runtime.NumGoroutine() > taban` sonucu `true` — bu, önceki derslerde defalarca
görülen boolean sızıntı ölçütünün burada üçüncü kez, ama yeni bir sebeple ortaya çıkışı: işçi
havuzunda sebep kapatmanın unutulmasıydı, yaymada sorumluluğun hiç verilmemesiydi, boru
hattında işin hiç bitmemesiydi. Burada sebep tamamen farklı — kapatılacak bir kanal bile yok,
sorun iş goroutine'inin **kimin dinlediğini hiç bilmemesi**. `dalSec`in `ctx.Done()` dalını
seçmesi, iş goroutine'ine hiçbir şey söylemiyor; iş goroutine'i kendi payına düşen hesaplamayı
yapıp sonucu göndermeye çalışmaya devam ediyor, tıpkı önceki dersteki ihmalkâr goroutine'in
iptali hiç denetlemeden işini bitirmesi gibi.

`taban`ın bu ölçümde iş goroutine'i başlamadan **hemen önce**, ikinci bir kez yakalanması da
dikkat çekici: ilk iki kurulumun kendi goroutine'leri (varsa) zaten sonlanmış olabilir, ölçümün
yalnız üçüncü kurulumdaki tek goroutine'e odaklanması gerekiyor. Bu, önceki derslerdeki
`runtime.NumGoroutine()` kullanımlarıyla aynı disiplin: taban her zaman ölçülecek goroutine'in
başlatılmasından hemen önce alınıyor, böylece karşılaştırma yalnız o tek goroutine'in var olup
olmadığını yansıtıyor — programın başından beri çalışan başka goroutine'lerin sayısı
karışmıyor.

Bu, dersin başlığındaki iki sözcüğün ("zaman aşımı" ve "kaynak sızıntısı") neden birlikte
anıldığını açıklıyor. Zaman aşımı, çağıranı bekleme durumundan **kurtarıyor** — çağıran artık
sonsuza kadar bloke değil, bir yanıtla (iptal ya da sonuç) devam edebiliyor. Ama zaman aşımının
kurtardığı yalnız **bekleyen taraf**; sonucu üretmesi beklenen iş, zaman aşımından tamamen
habersiz, kendi hızında çalışmaya devam ediyor. Çağıran "vazgeçtim" dediğinde iş goroutine'ine
bu haber hiç ulaşmıyor — tıpkı önceki dersteki gibi, bağlam kimseyi zorla durdurmuyor, yalnız
bekleyen tarafa bir çıkış kapısı açıyor.

Bu ölçümdeki iş goroutine'i kasıtlı olarak en yalın hâliyle kuruldu: tek bir gönderim, hiçbir
hesaplama yok. Gerçek bir sistemde bu goroutine bir ağ isteği, bir veritabanı sorgusu ya da bir
dosya okuması olurdu — kendi süresi olan, kesilmeden tamamlanmak isteyen bir iş. Zaman aşımı
o işin kendisini durdurmuyor; yalnız **çağıranın** o işi beklemekten vazgeçmesini sağlıyor.
İşin arkada gerçekten tamamlanıp tamamlanmadığı bu ölçümün konusu değil — burada kesin olan
tek şey, işin sonucunu **artık kimsenin almayacağı**, çünkü tamponsuz kanalın tek okuyucusu
zaten ayrılmış durumda.

Bunun pratik sonucu açık: bir zaman aşımı kurmak tek başına yeterli değil. İş goroutine'inin
kendisi de aynı bağlamı **paylaşmalı** ve kendi `ctx.Done()`unu denetlemeli — aksi hâlde
çağıran taraf "zaman aşımına uğradı" deyip devam ederken, arkada bırakılan iş sonsuza kadar
sürebiliyor. Bu ders bu ikinci adımı **kurmuyor**, yalnız kurulmadığında ne olduğunu ölçüyor;
iş goroutine'inin de aynı `ctx`i alıp kendi `select`inde denetlemesi, önceki iki derste
görülen düzeneğin (bir aşamaya `ctx.Done()` dalı eklemek) doğrudan tekrarı olurdu.

Bu konunun beş dersi, aynı sorunun beş ayrı yüzünü ölçtü ve hepsi aynı noktada buluşuyor: bir
goroutine, kendi `ctx.Done()`unu ya da kendi kapatılan kanalını **denetlemediği** sürece, ne
kadar iyi niyetli bir düzenek kurulursa kurulsun, dışarıdan gelen hiçbir sinyal onu durduramaz.
İşçi havuzunda bu denetim `for is := range isler`in kendisiydi; boru hattında her aşamanın
kendi girdi kanalıydı; burada ise iş goroutine'inin hiç yazmadığı, eksik kalan bir `select`
dalı. Beşi de aynı dersi farklı bir açıdan tekrar ediyor: durma, goroutine'in kendi kod
yolunda bir kontrol noktasına uğramasına bağlı, kontrol noktası yoksa dışarıdaki hiçbir sinyal
bir anlam taşımıyor.

#### Özet

- Zaman aşımı bir `select` deyiminin bir dal seçimidir; süre hiç ölçülmez, yalnız hangi dalın
  seçildiği yazılır.
- Sonuç önceden hazırsa ya da bağlam önceden iptal edilmişse, `select`in seçtiği dal
  belirlenimcidir — kurulumda yalnız bir dal gerçekten hazır olduğu için.
- İptal edilmiş bir bağlamın `select` açısından görünümü, süresi dolmuş bir zaman aşımı
  bağlamıyla aynıdır; `select` iptalin sebebini bilmez, yalnız `Done()`in kapalı olup
  olmadığını görür.
- Zaman aşımı bekleyen tarafı kurtarır ama sonucu üretmesi beklenen işi durdurmaz; iş
  goroutine'i kendi bağlamını denetlemediği sürece çalışmaya devam eder.
- Bu son sızıntı da bir boolean olarak ölçülür; sebebi önceki derslerdeki üç sebepten
  (unutulmuş kapatma, sahipsiz sorumluluk, bitmeyen iş) farklı — kimin dinlediğini bilmeyen
  bir gönderici.
- `context.WithTimeout` (göreli süre) ve `context.WithDeadline` (mutlak an), `select`
  açısından `context.WithCancel`den ayırt edilemez; üçü de kapanan bir `Done()` kanalı verir.

#### Sonraki Adım

Bu konunun beş dersi boyunca kurulan her düzenek — işçi havuzu, yayma ve toplama, boru hattı,
bağlam, zaman aşımı — aynı ilkeye dayandı: goroutine'ler birbirine **iletişimle** haber
veriyordu, bir kanal göndererek ya da bir kanalı kapatarak. Zaman aşımı bekleyeni kurtarır ama
beklenen işi durdurmaz; sıradaki konu paylaşılan duruma dönüyor — iletişim yerine ortak bir
değeri korumayı seçtiğinde ne değişiyor. Orada goroutine'ler artık birbirine kanal üzerinden
haber vermiyor, aynı belleğe erişimlerini bir kilitle sınırlıyor — ve durma kararı, kilidi
bırakan tarafın bir adımına bağlı kalmaya devam ediyor.

## Paylaşılan Durum

Karşılıklı dışlamanın kilit kapsamıyla ölçülmesi, toplu beklemenin sayaç sözleşmesi, bir kez çalıştırma ve atomik işlemlerin kilide göre yeri, ve yarış koşulu saptayıcısının test sırasında ne bulup ne bulamadığı.

### Dışlama Kilitleri

Source: https://academia.sh/tr/kurslar/go-eszamanlilik/dislama-kilitleri

Bir goroutine kilidi tutan başka bir goroutine kilidi bırakana kadar bloklanıyor ve durma sınıfı beklediği geldi oluyor. Aynı sayaç kilitli ve kilitsiz iki kurulumla 20 goroutine ve goroutine başına 100 artışla işletiliyor, ikisi de 2000 veriyor — çünkü kilitsiz kurulum gerçek bir yarış kurmuyor, her goroutine kendi gözesine yazıyor. Sınırlayıcı ölçüm, kilit kapsamı dışına taşan tek bir yazmayı kanalla zorlanmış bir sırayla modelliyor: iki artış uygulanıyor ama sonuç 101 yerine 1 çıkıyor.

Önceki dersin kapanışı bir sınır çizdi: zaman aşımı bekleyen goroutine'i kurtarıyor ama
beklenen işin kendisini durdurmuyor — iş goroutine'i hâlâ koşuyor, yalnız kimse onu
beklemiyor. Bu ders aynı sorudan uzaklaşıyor. Şimdiye kadarki her ders goroutine'lerin
birbirine bir **değer** aktarmasını ölçtü: kanaldan geçen bir sayı, bir bağlamın iptal
sinyali, bir zaman aşımının seçtiği dal. Bu derste goroutine'ler birbirine bir şey
göndermiyor; aynı **değeri** paylaşıyor. İletişim yerine ortak bir durumu korumayı seçmek
ne değiştiriyor?

Kanal tabanlı düzenekler bu sorudan hep kaçındı: bir kanaldan geçen değer kopyalanıyordu,
gönderen taraf değeri gönderdikten sonra ona dokunmuyordu, dolayısıyla iki goroutine'in
aynı belleğe aynı anda dokunması gibi bir durum hiç kurulmuyordu. Paylaşılan durum bu
kaçışı kapatıyor: birden çok goroutine gerçekten aynı değişkeni okuyor ve yazıyor. Kursun
ikinci sert kısıtı burada devreye giriyor — paylaşımın **korunmadığı** hâlini koşturup
yanlış bir sonuç üretmek bu derste yapılmıyor; onun yerine korumanın ne sağladığı ve
nerede bittiği ölçülüyor.

#### Kilidin Kapsamı

Karşılıklı dışlama kavramı — aynı anda yalnız bir yürütme biriminin bir kaynağa erişmesini
zorlamak — İşletim Sistemi Kavramları kursunda kuruldu ve orada serpiştirme sayılarak
ölçüldü; burada tekrarlanmıyor. Bu derste ölçülen şey kavramın kendisi değil, Go'nun onu
gerçekleştiren tek aracı: `sync.Mutex`, bir **dışlama kilidi**. Kilidin sunduğu sözleşme
kısa: `Lock` çağrısı kilidi tutan biri varsa döner ama önce **bloklanır**, `Unlock` kilidi
serbest bırakır. `Lock` ile `Unlock` arasında kalan satırlar kilidin **kapsamıdır** — kilit
yalnız bu satırların aynı anda tek bir goroutine tarafından çalıştırılmasını garanti eder,
kapsam dışında kalan hiçbir şeye karışmaz. Bu kapsam, İşletim Sistemi Kavramları kursunun
**kritik bölge** dediği şeyin Go'daki karşılığı: kritik bölge kavramı orada nasıl
sınırlandığından bağımsız kuruldu, `sync.Mutex` burada onu iki çağrı arasındaki bir aralık
olarak somutlaştırıyor.

`sync.Mutex`in bir bildirim dışında hiçbir kurulum gerektirmemesi de sözleşmenin bir
parçası: `var kilit sync.Mutex` yazıldığı anda kilit kullanıma hazır, çünkü kilidin sıfır
değeri zaten kilitlenmemiş bir kilit. Go Temelleri kursunun sıfır değer bahsine burada tek
cümlelik bir atıf yeterli — kilidin kendisi bu kursun ölçü ekseni değil, yalnız bir araç.

`Artir` yöntemindeki `defer s.mu.Unlock()` yazımı da bu sözleşmenin bir parçası: kilidi
bırakan çağrı, yöntemin gövdesi nereden dönerse dönsün — normal dönüş, erken dönüş, ya da
bir panikle — çalışır. Kilidi elle, gövdenin sonuna yazan bir `Unlock()` çağrısıyla
bırakmak, gövde ortasında bir erken dönüş eklendiğinde kilidin hiç bırakılmadan kalmasına
açık kapı bırakır; `defer` bu boşluğu kapatıyor. Bu ders bu boşluğu ayrıca koşturmuyor,
çünkü konu bu dersin sınırlayıcı ölçümünde zaten farklı bir biçimde — kilit kapsamının
kendisinin yanlış çizilmesi olarak — ele alınıyor.

Bu sözleşmenin ilk sonucu, kursun ilk dersinden beri sorulan soruya doğrudan bağlanıyor:
kilidi bekleyen bir goroutine nasıl duracak? Yanıt ortak tanımın kendi satırlarından biri
— kilidi tutan goroutine `Unlock` çağırdığında, bekleyen goroutine'in `Lock` çağrısı geri
döner ve goroutine devam eder. Bu, dört durma sınıfından **beklediği geldi**'nin bir
örneği: bekleyen goroutine kendi başına durmuyor, başka bir goroutine'in attığı bir adım
— burada kilidi bırakmak — onu serbest bırakıyor. Birden çok goroutine aynı kilidi
beklerken hangisinin önce devam edeceği konusunda `sync.Mutex` bir sıra garantisi
vermiyor; bu, İşletim Sistemi Kavramları kursunun açlık bahsiyle aynı ailede bir konu ve
burada yalnız bir cümleyle anılıyor, çünkü bu dersin ölçtüğü tek bekleyen için önemsiz.

```go
// sayac.go — dislama kilidiyle korunan sayac ve kilit sinirini modelleyen yari korumali sayac
package main

import "sync"

// KorumaliSayac, dislama kilidiyle korunan bir tamsayi tasir.
type KorumaliSayac struct {
	mu    sync.Mutex
	deger int
}

func (s *KorumaliSayac) Artir() {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.deger++
}

// YariKorumali, artis islemini ikiye ayirir: okuma kilit icinde, yazma kilit disinda.
// Bu, kilit kapsaminin disinda kalan tek satirin modelidir.
type YariKorumali struct {
	mu    sync.Mutex
	deger int
}

func (y *YariKorumali) OkuVeKilidiBirak() int {
	y.mu.Lock()
	defer y.mu.Unlock()
	return y.deger
}

func (y *YariKorumali) YazKilitsiz(yeni int) {
	y.deger = yeni
}
```

`KorumaliSayac` sözleşmeyi doğru kuruyor: `Artir`in okuduğu ve yazdığı her şey `Lock` ile
`Unlock` arasında kalıyor. `YariKorumali` ise bilerek bozuk kuruldu — okuma kilit altında,
yazma kilidin tamamen dışında, iki ayrı yöntemde. Bu ikinci tip aşağıdaki sınırlayıcı ölçüm
için; ilk ölçüm yalnız `KorumaliSayac`ı kullanıyor.

#### Üç Ölçüm, Bir Koşum

```go
// main.go — kilit kapsami, kilitli-kilitsiz karsilastirma, kilit sinirinin modellenmesi
package main

import (
	"fmt"
	"sync"
)

func main() {
	fmt.Println("-- kilidi bekleyen goroutine, kilit birakilinca duruyor --")
	var kilit sync.Mutex
	rapor := make(chan string, 1)
	kilit.Lock()
	go func() {
		kilit.Lock()
		kilit.Unlock()
		rapor <- "durdu: beklediği geldi"
	}()
	kilit.Unlock()
	fmt.Println(<-rapor)

	fmt.Println()
	fmt.Println("-- kilitli ve kilitsiz kurulumda sonuc --")
	const goroutineSayisi = 20
	const artisSayisi = 100

	kilitli := &KorumaliSayac{}
	var grup1 sync.WaitGroup
	grup1.Add(goroutineSayisi)
	for i := 0; i < goroutineSayisi; i++ {
		go func() {
			defer grup1.Done()
			for j := 0; j < artisSayisi; j++ {
				kilitli.Artir()
			}
		}()
	}
	grup1.Wait()

	paylar := make([]int, goroutineSayisi)
	var grup2 sync.WaitGroup
	grup2.Add(goroutineSayisi)
	for i := 0; i < goroutineSayisi; i++ {
		go func(indeks int) {
			defer grup2.Done()
			for j := 0; j < artisSayisi; j++ {
				paylar[indeks]++
			}
		}(i)
	}
	grup2.Wait()
	kilitsizToplam := 0
	for _, p := range paylar {
		kilitsizToplam += p
	}

	fmt.Printf("kilitli toplam   : %d\n", kilitli.deger)
	fmt.Printf("kilitsiz toplam  : %d\n", kilitsizToplam)
	fmt.Printf("ikisi ayni mi    : %v\n", kilitli.deger == kilitsizToplam)

	fmt.Println()
	fmt.Println("-- sinirlayici olcum: kilit kapsami disina tasan tek satir modelleniyor --")
	yarim := &YariKorumali{}
	adim1 := make(chan int)
	adim2 := make(chan struct{})
	bitti := make(chan struct{})

	go func() {
		eski := yarim.OkuVeKilidiBirak()
		adim1 <- eski
		<-adim2
		yarim.YazKilitsiz(eski + 1)
		close(bitti)
	}()

	eskiDeger := <-adim1
	yarim.mu.Lock()
	araDeger := yarim.deger
	yarim.deger = araDeger + 100
	yarim.mu.Unlock()
	close(adim2)
	<-bitti

	fmt.Printf("g1 okudugu eski deger      : %d\n", eskiDeger)
	fmt.Printf("iki artis uygulandi (+1 ve +100), beklenen toplam: 101, gerceklesen: %d\n", yarim.deger)
}
```

```
-- kilidi bekleyen goroutine, kilit birakilinca duruyor --
durdu: beklediği geldi

-- kilitli ve kilitsiz kurulumda sonuc --
kilitli toplam   : 2000
kilitsiz toplam  : 2000
ikisi ayni mi    : true

-- sinirlayici olcum: kilit kapsami disina tasan tek satir modelleniyor --
g1 okudugu eski deger      : 0
iki artis uygulandi (+1 ve +100), beklenen toplam: 101, gerceklesen: 1
```

**PD1.** İlk satır ortak tanımın kendi ölçüsünü doğrudan koşturuyor: `main`, kilidi
tutarken bir goroutine başlatıyor, o goroutine `Lock` çağrısında bekliyor, `main`
`Unlock` çağırınca goroutine devam edip durma sebebini bildiriyor. Goroutine ve Kanallar
dersinde ölçülen 2/5/2/3 dağılımının "beklediği geldi" satırlarından biri tam olarak bu
düzenek — kilit burada beşinci düzenektir.

**PD2.** İkinci ölçüm yirmi goroutine'i, her biri yüz kez artırarak, iki ayrı yolla
koşturuyor. `kilitli` kurulumda hepsi aynı `KorumaliSayac`ı paylaşıyor ve her artış
`Lock`/`Unlock` çiftinin arasında kalıyor; sonuç deterministik biçimde 2000. `kilitsiz`
kurulumda ise her goroutine kendi indeksine ait ayrı bir dilim gözesini artırıyor — yirmi
goroutine yirmi **farklı** bellek konumuna yazıyor, hiçbiri aynı konuma dokunmuyor. İki
kurulum da 2000 veriyor ve bu iki sayı **aynı** çünkü ikisinde de gerçekte kilit gerektiren
bir yarış hiç kurulmadı: kilitli kurulumda kilit fazladan bir koruma sağlıyor ama zaten
paylaşılmayan bir kaynağı koruyor değil, kilitsiz kurulum da paylaşım yerine ayrık bellek
kullanarak yarışı baştan dışlıyor. Sonuçların eşit çıkması "kilitsiz de güvenlidir"
anlamına gelmiyor; yalnızca bu iki kurulumun ikisinin de güvenli olduğu, ama farklı
sebeplerle güvenli olduğu anlamına geliyor. Gerçekten paylaşılan tek bir değişkene
kilitsiz yazan goroutine'lerin ürettiği sonucu bu derste koşturmuyoruz — o karşılaştırma
bu kursun ikinci sert kısıtına giriyor ve yalnız saptayıcının verdiği yanıtla ölçülüyor.

Kilidin kendisinin bir bedeli var: yirmi goroutine aynı kilit için sırayla bekliyor, yani
her `Artir` çağrısı bir öncekinin bıraktığı kilidi devralıyor. Bu **çekişme** — birden çok
goroutine'in aynı kilide aynı anda ulaşmaya çalışması — kilitli kurulumu kilitsiz
kurulumdan yavaşlatabilir, çünkü kilitsiz kurulumda hiçbir goroutine bir diğerini
beklemiyor. Ama bu ders süre ölçmüyor; ölçülen tek şey doğruluk, iki kurulumun da aynı
toplama ulaşması. Çekişmenin bedeli gerçek olsa da, bu dersin sorusunun dışında kalıyor —
kilidin var oluş sebebi hız değil, doğruluk.

#### Sınırlayıcı Ölçüm: Kilit Kapsamının Sınırı

**PD3.** `YariKorumali`, `KorumaliSayac`la aynı kilidi taşıyor ama sözleşmeyi bozuyor:
değeri okuyup kilidi hemen bırakıyor, sonra kilidin **dışında** yeni değeri yazıyor.
Ölçüm bu boşluğu rastgele bir zamanlamaya bırakmıyor — iki kanal (`adim1`, `adim2`) araya
girerek sırayı kesin biçimde zorluyor: goroutine önce eski değeri okuyup bildiriyor,
`main` bu bildirimi aldıktan sonra kendi kilidini alıp değeri 100 artırıyor ve bırakıyor,
ancak o zaman goroutine'e devam etmesi için sinyal veriliyor. Goroutine devam ettiğinde
kilidin dışındaki `YazKilitsiz` çağrısı, kendi okuduğu **eski** değere göre hesapladığı
`eski + 1`i yazıyor — `main`in araya girip yazdığı 100'ü hiç görmeden üzerine yazıyor.
İki artış birden uygulandı (biri +1, biri +100) ama sonuç 101 değil, 1.

Bu bir yarış koşulunun koşturulması değil, tam tersi: iki adım arasındaki sıra kanallarla
tamamen belirlendiği için `main`in yazması ile goroutine'in yazması arasında hiçbir
zamanlama belirsizliği yok, ikisi de her koşumda aynı sırayla, aynı şekilde gerçekleşiyor.
Yarış koşulu saptayıcısı bu koda bakıp herhangi bir uyarı **vermez** — okuma ve yazma
arasında kanal üzerinden kurulan bir sıra ilişkisi var, iki goroutine aynı anda aynı
belleğe dokunmuyor. Kusur bir zamanlama hatası değil, bir **tasarım** hatası: kilit
kapsamı, "değeri koru" sözleşmesinin gerektirdiği yerin tamamını kapsamıyor. Değeri okumak
ile yazmak arasında geçen tüm işlem — burada `eski + 1` hesabı ve onu geri yazma — kilidin
altında kalmalıydı; kilit yalnızca okuma anını korudu, kararı verme ve geri yazma anını
korumadı.

Bu, dersin sınırlayıcı okuması: kilit **kapsamı kadar** korur, ne bir satır fazla ne bir
satır eksik. Korunması gereken şey tek bir değişken değil, o değişken üzerindeki **bütün
bir işlemdir** — oku, hesapla, yaz üçlüsü aynı `Lock`/`Unlock` çifti içinde kalmadığı sürece
kilidin varlığı hiçbir şey garanti etmiyor. `KorumaliSayac.Artir` bunu doğru yapıyor: okuma,
artırma ve yazma tek bir ifadede (`s.deger++`) ve tek bir kilit kapsamında. `YariKorumali`
aynı üç işlemi iki ayrı yönteme, iki ayrı kilit kapsamına bölerek sözleşmeyi kırıyor —
kilidin kendisi hiçbir hata vermiyor, çünkü kilidin görevi yalnızca kapsamını korumak,
kapsamın doğru çizilip çizilmediğine karar vermek değil.

`YariKorumali`nin iki yöntemi ayrı ayrı okunduğunda hiçbiri şüpheli görünmüyor:
`OkuVeKilidiBirak` kilidi doğru kullanıyor, `YazKilitsiz` de kendi başına bakıldığında bir
sorun taşımıyor, çünkü tek bir alana atama yapıyor. Kusur ancak iki yöntemin **birlikte**
çağrılma biçiminde ortaya çıkıyor — aralarında kilidin bırakılıp yeniden alınabileceği bir
boşluk var ve bu boşluk kaynakta okunarak görülemiyor, yalnız iki çağrının arasına başka
bir goroutine'in girip giremeyeceği sorularak görülebiliyor. Bu, paylaşılan durumu
tasarlarken tekrar eden bir kalıp: bir türün dışa açtığı yöntemler "oku" ve "yaz" gibi ayrı
parçalara bölündüğünde, o parçaları çağıran kod parçaların arasında kilidin bırakıldığını
bilmeyebilir. `KorumaliSayac.Artir`in tek bir yöntemde kalması bilinçli bir seçim: dışa
açılan sözleşme "bir artışı bölünmez biçimde uygula" oluyor, çağıran taraf okuma ile yazma
arasındaki boşluğu hiç görmüyor çünkü öyle bir boşluk kaynakta hiç yok.

#### Kilit mi, Kanal mı

Önceki konu boyunca her paylaşım bir kanaldan geçirilerek kuruldu: bir değeri değiştirmek
isteyen goroutine, değeri tutan tek bir goroutine'e bir mesaj gönderiyor, değişikliği o
goroutine kendi sırasında uyguluyordu. Bu ders aynı işi kilitle yapıyor: değeri tutan
`KorumaliSayac`, kendi kilidini taşıyor ve değişikliği isteyen her goroutine doğrudan o
belleğe erişiyor, aralarına bir aracı goroutine girmiyor. İkisi de aynı sonucu verebilir —
önceki konudaki işçi havuzu kalıbı bunu bir kanalla sağlamıştı, bu ders aynı korumayı bir
kilitle sağlıyor. Ortak tanımın üçüncü iddiası burada tekrar geçerli: durduran şey kanal ya
da kilidin kendisi değil, birinin attığı bir adım; iletişim ile paylaşım arasındaki seçim
bir kurtuluş değil, bir **tercih**.

Bu tercihin yönü basit bir soruyla bulunabilir: korunan şey bir bellek konumu mu, yoksa bir
sıradaki iş mi? `KorumaliSayac.deger` yalnız bir tamsayı, üzerinde yapılan tek işlem
artırma; bu işlemi bir kanal üzerinden tek bir sahibe yönlendirmek, doğrudan kilitlemekten
daha fazla dolayım gerektirir ama fazladan bir garanti eklemez. Değer daha karmaşık bir
durumsa — birden çok alanın birlikte tutarlı kalması gerekiyorsa, ya da işlemler arasında
bir sıra kurulması gerekiyorsa — kanalın tek sahipliği daha doğal bir sınır çizer. Bu ders
yalnız kilidin kendi sözleşmesini ölçüyor; iki yaklaşımın hangi durumda tercih edileceği
kursun kapanışına bırakılan daha geniş bir soru.

#### Özet

- `sync.Mutex`, Go'nun karşılıklı dışlamayı gerçekleştiren kilidi; `Lock` ile `Unlock`
  arasında kalan satırlar kilidin kapsamını oluşturuyor ve yalnız bu kapsam korunuyor.
- Kilidi bekleyen bir goroutine, kilidi tutan başka bir goroutine `Unlock` çağırınca
  devam ediyor — dört durma sınıfından beklediği geldi'nin bir örneği.
- Aynı sayaç kilitli ve kilitsiz iki kurulumla 2000'e ulaşıyor; eşitlik güvenlik kanıtı
  değil, çünkü kilitsiz kurulum paylaşılan tek bir değişkene değil ayrık bellek
  gözelerine yazarak yarışı zaten dışlıyor.
- Kilit kapsamı dışına taşan tek bir yazma, kanalla zorlanmış bir sırayla modellendi:
  iki artış uygulandı ama sonuç 101 yerine 1 çıktı, çünkü kilit yalnız okumayı korudu.
- Kilit, korunması gereken **bütün işlemi** — okuma, hesaplama, yazma — aynı kapsamda
  tutmayı gerektiriyor; kapsamın nereye çizildiği kilidin kendisinin denetleyemediği bir
  karar.

#### Sonraki Adım

Bu derste tek bir goroutine'in kilit bekleyip beklemediği ölçüldü. Ama çoğu paylaşılan
durum sorunu tek bir bekleyenle sınırlı değil: bir goroutine grubunun **tamamının**
bitmesini beklemek, her birinin ayrı ayrı bir kilit bırakmasını beklemekten farklı bir
sözleşme gerektiriyor. Sıradaki ders bu toplu beklemeyi kuran aracı — sayaç tabanlı
`sync.WaitGroup`ı — ve onun kendi sözleşmesini, sayacın ne zaman sıfırlanacağını ve
sıfırlanmazsa ne olacağını ölçüyor.

### Bekleme Grupları

Source: https://academia.sh/tr/kurslar/go-eszamanlilik/bekleme-gruplari

Beş işçinin tamamlanmasını bir sayaç bekliyor: Add başlamadan önce sayacı beşe çıkarıyor, her işçi kendi Done'unu defer ile garanti ediyor, Wait sayaç sıfıra inince dönüyor — durma sınıfı yine beklediği geldi. Sayaç hiç sıfırlanmazsa Wait hiçbir zaman dönmüyor ve bu sızıntı bir boolean'la ölçülüyor; Done bir kez fazladan çağrıldığında sayaç eksiye düşüyor ve bekleme grubu panikliyor, panik recover ile yakalanıp yalnız panik=true yazılıyor. Sınırlayıcı ölçüm: beş işçinin hangisinin önce bittiği hiç ölçülmüyor, yalnız hepsinin bittiği ölçülüyor — sonuçlar toplanıp sıralanarak basılıyor.

Önceki ders tek bir kilidi bekleyen tek bir goroutine'i ölçtü. Ama paylaşılan durumu
koruyan tarafın kendisi çoğu zaman tek bir goroutine değil, bir **grup**: bir işi paylara
bölüp paralel yürüten birden çok goroutine ve onların tamamının bitmesini bekleyen bir
ana goroutine. Kilit bu beklemeyi kurmuyor — kilit yalnız aynı anda kaç goroutine'in bir
kaynağa dokunabileceğini sınırlıyor, kaç goroutine'in **bittiğini** saymıyor. Bu derste
ölçülen araç bu sayımı yapan `sync.WaitGroup`, bir **bekleme grubu**.

Bir bekleme grubunu bir kanalla taklit etmek mümkün — her işçi bittiğinde bir kanala boş
bir değer gönderir, `main` de o kanaldan tam olarak işçi sayısı kadar değer okur. Önceki
konudaki işçi havuzu ve yayma-toplama dersleri gerçekten böyle yaptı: sonuç kanalının
kendisi hem değeri taşıyor hem de kaç sonucun geldiğini sayıyordu. Bekleme grubu bu iki
işi ayırıyor: kaç işçinin bittiğini saymak için ayrı bir sayaç, değeri taşımak için (bu
derste olduğu gibi) ayrı bir kanal ya da hiç kanal. Bu ayrım, işçilerin ürettiği bir
değer olmadığında — yalnız bir işin **bittiğini bilmek** yeterli olduğunda — daha az
düzenek gerektiriyor.

#### Sayaç Sözleşmesi

Bekleme grubu bir dilim, bir kanal ya da bir liste değil — içinde tek bir tamsayı sayaç
tutan bir tip. Sözleşmesi üç çağrıya dayanıyor: `Add(n)` sayacı `n` artırıyor, `Done()`
sayacı bir azaltıyor, `Wait()` sayaç sıfıra inene kadar bloklanıyor. Bu üç çağrının doğru
sırayla kullanılması bir kural: `Add` her zaman ilgili goroutine **başlamadan önce**
çağrılmalı, `Done` her goroutine'in **her çıkış yolunda** çalışmalı — bu yüzden `defer` ile
yazılıyor, tıpkı önceki dersteki `Unlock` gibi — ve `Wait` sayaç sıfıra inmeden asla
dönmüyor.

`Add`in goroutine başlamadan önce çağrılması bir üslup tercihi değil. `Add` yerine
goroutine'in **kendi içinde**, ilk satırında çağrılsaydı, `main`in kendisi `grup.Wait()`e
o goroutine daha `Add`ini hiç çalıştırmadan ulaşabilirdi — bu durumda sayaç henüz sıfır
olduğu için `Wait` hemen dönebilir, henüz başlamamış bir işi hiç beklemeden. Bu, kod
kaynağına bakarak fark edilmesi güç bir hata sınıfı, çünkü hangi sırayla çalışacağı
goroutine zamanlamasına bağlı ve bu ders böyle bir kurulumu bilerek koşturmuyor — sonuç
koşumdan koşuma değişebileceği için bu kursun belirlenimcilik kısıtına girerdi. Kural
bunun yerine kaynağın kendisinde okunabilir: örnekteki `grup.Add(1)` satırı, döngünün
her adımında, `go` sözcüğünden önce duruyor.

```go
// main.go — sayac sozlesmesi, sifirlanmayan sayacta sizinti, sayacin sifirin altina inmesi
package main

import (
	"fmt"
	"runtime"
	"sort"
	"sync"
)

func main() {
	fmt.Println("-- sayac sozlesmesi: Add once, Done her yolda, Wait sifirda doner --")
	const isSayisi = 5
	var grup sync.WaitGroup
	tamamlanan := make(chan string, isSayisi)
	agirliklar := []int{3, 1, 4, 1, 5}
	for i := 0; i < isSayisi; i++ {
		grup.Add(1)
		go func(indeks, agirlik int) {
			defer grup.Done()
			toplam := 0
			for j := 0; j < agirlik*1000; j++ {
				toplam += j
			}
			tamamlanan <- fmt.Sprintf("is-%d (agirlik=%d, toplam=%d)", indeks, agirlik, toplam)
		}(i, agirliklar[i])
	}
	grup.Wait()
	fmt.Println("Wait dondu: durdu, beklediği geldi")

	close(tamamlanan)
	var raporlar []string
	for r := range tamamlanan {
		raporlar = append(raporlar, r)
	}
	sort.Strings(raporlar)
	for _, r := range raporlar {
		fmt.Println(r)
	}

	fmt.Println()
	fmt.Println("-- sifirlanmayan sayac: Add var, Done hic cagrilmiyor --")
	taban := runtime.NumGoroutine()
	var sizanGrup sync.WaitGroup
	sizanGrup.Add(1)
	bitti := make(chan struct{})
	go func() {
		sizanGrup.Wait()
		close(bitti)
	}()
	select {
	case <-bitti:
		fmt.Println("Wait dondu: beklenmiyordu")
	default:
		fmt.Println("Wait dondu mu: hayir, sayac hic sifirlanmadi")
	}
	fmt.Println("durmayan goroutine kaldi mi:", runtime.NumGoroutine() > taban)

	fmt.Println()
	fmt.Println("-- sayacin sifirin altina inmesi: panik --")
	panikVarMi := calistirVeKurtar(func() {
		var asiriGrup sync.WaitGroup
		asiriGrup.Add(1)
		asiriGrup.Done()
		asiriGrup.Done()
	})
	fmt.Println("panik:", panikVarMi)
}

// calistirVeKurtar verilen islevi calistirir, panikledi mi diye dondurur; yigit izi basilmaz.
func calistirVeKurtar(islev func()) (panikVarMi bool) {
	defer func() {
		if recover() != nil {
			panikVarMi = true
		}
	}()
	islev()
	return false
}
```

```
-- sayac sozlesmesi: Add once, Done her yolda, Wait sifirda doner --
Wait dondu: durdu, beklediği geldi
is-0 (agirlik=3, toplam=4498500)
is-1 (agirlik=1, toplam=499500)
is-2 (agirlik=4, toplam=7998000)
is-3 (agirlik=1, toplam=499500)
is-4 (agirlik=5, toplam=12497500)

-- sifirlanmayan sayac: Add var, Done hic cagrilmiyor --
Wait dondu mu: hayir, sayac hic sifirlanmadi
durmayan goroutine kaldi mi: true

-- sayacin sifirin altina inmesi: panik --
panik: true
```

**PD4.** Beş işçi beş ağırlıkla (`3, 1, 4, 1, 5`) başlatılıyor — biri iki kez geçiyor — ve her
biri kendi toplamını hesaplayıp bir kanala yazıyor. `grup.Wait()` ancak beşi de kendi `Done`sunu
çağırdıktan sonra dönüyor — bu, kursun ilk dersinden beri işlenen durma sınıfının bir örneği daha:
`main`in kendisi bir goroutine gibi bekliyor, beklediği şey (sayacın sıfıra inmesi) geldiğinde
duruyor. Bekleme grubu burada kilitten farklı bir sözleşme sunuyor: kilit tek bir kaynağı
korurken, bekleme grubu **kaç işin bittiğini** sayıyor. Ortak tanımın üçüncü iddiası bunu doğrudan
destekliyor: beş "beklediği geldi" satırı beş ayrı düzenekle kurulmuştu (tamponsuz alım, kapanan
kanal, kilit, bekleme grubu, `select`) ve düzenek değişse de bağımlılık değişmiyordu. Önceki ders
kilidi, bu ders bekleme grubunu kullanıyor — ikisi de aynı ailenin, "başka bir goroutine'in
adımına bağlı olarak durma" ailesinin, birer üyesi.

Ağırlıkların kendisi de bilerek farklı seçildi: `1` ağırlığı iki kez tekrarlanıyor
(`is-1` ve `is-3`), geri kalan üçü (`3, 4, 5`) birbirinden farklı. Bu, bekleme grubunun
işçileri **hiçbir şekilde** ayırt etmediğini gösteriyor — sayaç yalnız kaç `Done`
çağrıldığını tutuyor, hangi goroutine'in hangi işi yaptığını bilmiyor. `is-1` ile `is-3`
aynı ağırlıkla aynı toplamı (`499500`) üretiyor ve bekleme grubu için bu iki işçi
arasında hiçbir fark yok; ayrımı yalnız çıktıdaki `is-` önekleri taşıyor, bekleme grubunun
kendisi değil.

**PD5.** Beş işçi beş farklı sürede bitiyor — ağırlığı yüksek olan daha uzun döngü
çalıştırıyor — ama bu ders hangisinin **önce** bittiğini hiç ölçmüyor. Kanala yazılan
raporlar `tamamlanan` kanalından okunup bir dilime toplanıyor, sonra `sort.Strings` ile
sıralanıp basılıyor; kanaldan **geliş sırası** hiç yazdırılmıyor, çünkü o sıra goroutine
zamanlamasına bağlı ve koşumdan koşuma değişebilir. Bekleme grubunun garanti ettiği tek
şey sayacın sıfıra indiği an; hangi `Done` çağrısının önce geldiği bir garanti değil. Bu,
dersin sınırlayıcı ölçümü: **beş işçinin hepsi bitti** ölçülüyor, **hangisi önce bitti**
ölçülemiyor — ölçülmeye çalışılsaydı bu dersin de çıktısı koşumdan koşuma değişir ve
doğrulama sapma verirdi.

`grup` değişkeni `main`in yerel bir değişkeni ve her goroutine'e bir kapanış üzerinden
**işaretçi olmadan** erişiliyor — Go'nun kapanışları çevreleyen değişkenleri kendi
adresleriyle yakaladığı için `grup.Add`, `grup.Done` ve `grup.Wait` hep aynı sayaca
dokunuyor. Bekleme grubu bir yapı ve Go Temelleri kursunda ölçülen değerle aktarım
kuralı burada da geçerli: `grup`ün bir **kopyası** alınıp bir işleve değerle geçirilseydi,
o kopyanın kendi sayacı olurdu ve `main`in kendi sayacından bağımsız kalırdı — sözleşme
sessizce bozulurdu. İncelemenin görevi (Go Komut Ailesi dersinde ölçülen inceleyicinin
işi) tam olarak bunun gibi kalıpları yakalamak; bu ders böyle bir kopyalamayı bilerek
kurup koşturmuyor, çünkü bu, önceki dersteki kilidin dışına taşan tek satır gibi, kaynağı
okuyarak fark edilmesi güç bir hata sınıfı olurdu.

Kilit ile bekleme grubu burada iki ayrı sorumluluğu paylaşıyor. Önceki dersteki
`KorumaliSayac`, aynı değere birden çok goroutine'in aynı anda yazmasını engellemeyi
üstlenmişti; bu değeri kim koruyorsa koruyor olsun, "hepsi bitti mi" sorusuna yanıt
vermiyordu. Bu dersteki `tamamlanan` kanalı ile `grup` de tersini yapıyor: hangi işçinin
işi bittiğini sayıyor, ama işçilerin kendi aralarında paylaştığı bir belleğe hiç
karışmıyor — beş işçi burada zaten hiçbir ortak değişkene yazmıyor, her biri kendi yerel
`toplam`ını hesaplayıp kanala gönderiyor. Gerçek bir programda bu iki araç birlikte
görülüyor: birden çok goroutine ortak bir değeri kilitle koruyup her biri işini bitirince
`Done` çağırabiliyor, `main` da `Wait` ile hepsinin bitmesini bekleyip ancak o zaman
korunan değeri okuyabiliyor. Kilit **neyin** korunacağını, bekleme grubu **ne zaman**
okunmaya hazır olduğunu belirliyor; bu iki soru birbirinin yerini tutmuyor.

#### Sıfırlanmayan Sayaç: Sızıntı

**PD6.** İkinci kurulum sözleşmeyi bilerek yarım bırakıyor: `sizanGrup.Add(1)` çağrılıyor
ama karşılığındaki `Done` hiçbir goroutine'de hiç çağrılmıyor. Sayaç bir daha asla sıfıra
inmeyeceği için `sizanGrup.Wait()`i çağıran goroutine sonsuza dek bloklu kalıyor — bu,
dördüncü durma sınıfının kendisi: **hiç durmadı**. Bu bloklanmayı beklemeden denetlemek
için `Wait`i ayrı bir goroutine içine alıp bir kapatma kanalıyla sarıyoruz; `select`in
`default` dalı, o kanaldan hiçbir zaman bir değer gelmeyeceğini — çünkü sayaç matematiksel
olarak asla sıfıra inemez — anında ve kesin biçimde gösteriyor. Bu bir zamanlama tahmini
değil: `default` dalının seçilmesi hiçbir koşumda değişmiyor, çünkü `Wait`in dönmesi
zamanlamaya değil sayacın değerine bağlı ve o değer hiç değişmiyor.

**PD7.** `runtime.NumGoroutine()` çağrısı taban sayıyla karşılaştırılıp yalnız bir
`true`/`false` üretiyor; kaç goroutine'in kaldığı ayrıca sayılmıyor. Bu, ortak tanımın
ikinci iddiasının bir başka örneği: durmamak Go çalışma zamanı tarafından hiçbir hata ya
da uyarıya dönüştürülmüyor, programın geri kalanı normal biçimde çalışmaya devam ediyor —
yalnız o tek goroutine hiçbir zaman bitmiyor. Bu sessizlik, Goroutine dersinde ölçülen
kanal sızıntılarıyla aynı aileden: bekleme grubu da kanal da, karşı tarafın sözleşmesini
yerine getirmemesi durumunda kendiliğinden bir hata üretmiyor.

Bekleme grubunun kendisinde bir iptal mekanizması yok — `Wait` çağrısını dışarıdan durdurmanın
bir yolu bulunmuyor, sayaç sıfıra inene kadar bekler. Bağlam paketinin zaman aşımı dersinde
ölçülen `ctx.Done()` deseni bu boşluğu dolduruyor, ama bekleme grubunun kendisi böyle bir çıkış
sunmuyor; bu yüzden gerçek bir programda sonsuz bekleme riski taşıyan bir `Wait` çağrısı,
çoğunlukla bir zaman aşımıyla birlikte, ayrı bir goroutine ve kanal üzerinden — tam olarak bu
derste `bitti` kanalıyla yapıldığı gibi — sarmalanıyor. Bu ders o sarmalamayı yalnızca sızıntıyı
denetlemek için kullandı; gerçek bir zaman aşımı kurup beklemeyi kesmek Zaman Aşımı ve Son
Tarihler dersinin konusuydu ve burada tekrarlanmıyor.

#### Sayacın Sıfırın Altına İnmesi: Panik

**PD8.** Üçüncü kurulum tam tersi bir hatayı modelliyor: `Add(1)` bir kez çağrılıyor ama
`Done()` iki kez çağrılıyor. Bekleme grubu bunu sessizce görmezden gelmiyor — sayaç
negatife düştüğü anda **panikliyor**, çünkü negatif bir sayaç kütüphanenin kendi iç
tutarlılığını bozuyor ve bu durumu susturmak yerine hemen bildirmek Go standart
kütüphanesinin seçimi. `calistirVeKurtar`, önceki derslerin kalıbını sürdürerek paniği
`recover` ile yakalıyor ve yalnız `panik=true`/`panik=false` yazıyor; panik metninin
kendisi hiçbir yerde basılmıyor, çünkü o metin bir yığıt izi taşıyor.

Panik metninin basılmaması yalnız bir yığıt izi kaygısı değil. Standart kütüphanenin bu
paniğe verdiği metin, kaynağı derleyen sürüme bağlı olarak değişebilir — kelimesi kelimesine
aynı kalacağının garantisi yok, çünkü bu metin dilin belirttiği bir sözleşmenin parçası
değil, kütüphanenin iç uygulamasının bir ayrıntısı. `panik=true`/`panik=false` bunun
yerine dilin garanti ettiği tek şeyi ölçüyor: sayaç negatife düştüğünde bir panik
**oluyor mu, olmuyor mu**. Kanal Kapatma ve Aralık dersinde kapalı bir kanala ikinci kez
gönderim yapmanın da aynı biçimde ölçülmesi bu kursun tekrar eden bir alışkanlığı: paniğin
kendisi ölçülüyor, paniğin metni değil.

Bu iki hatalı kurulum ilginç bir karşıtlık kuruyor: sayacın hiç sıfırlanmaması **sessiz**
kalıyor, sayacın fazla sıfırlanması ise **yüksek sesle** bildiriliyor. Bu fark bekleme
grubunun kendi tasarımından geliyor, iki hatanın önemine dair bir yargı değil. Sayaç
sıfırın altına inebilir çünkü bekleme grubu her `Done` çağrısında sayacı denetliyor ve
negatif bir değeri hemen yakalıyor; sayacın hiç sıfırlanmaması ise bekleme grubunun
denetleyebileceği bir durum değil — kütüphane, kaç `Add` çağrısının karşılıksız kaldığını
bilemez, çünkü "karşılıksız kalmak" sonsuza kadar sürebilecek bir bekleyiş, bir hata değil.
Panik anlık bir olay olduğu için yakalanabiliyor; sızıntı bir olay değil, bitmeyen bir
durum olduğu için yakalanamıyor, yalnız dışarıdan **denetlenebiliyor**.

Bu üç kurulum bekleme grubunun sözleşmesini üç yönden sınıyor: sayaç doğru sıfırlandığında
`Wait` durur, hiç sıfırlanmadığında sonsuza dek durmuyor, fazla sıfırlandığında panikliyor.
Sözleşmenin üç ihlali de aynı kökten geliyor — `Add` ile `Done` çağrılarının sayısının
birbirine denk düşmesi gerekiyor ve bu denkliği derleyici değil çalışma zamanı
denetliyor. Önceki derste kilidin sözleşmesi neyin **kilit altında** kalması gerektiğiydi;
burada sözleşme kaç `Add`in kaç `Done`a karşılık geldiği. İkisi de derleyicinin hiç
göremediği bir kural: bir kaynağı okuyan biri, `Add` ile `Done` çağrılarının sayıca eşit
olup olmadığını yalnız kaynağı **izleyerek**, çalıştırarak görebiliyor.

#### Özet

- `sync.WaitGroup`, `Add`, `Done` ve `Wait` üçlüsüyle çalışan bir sayaç; `Wait` sayaç
  sıfıra inene kadar bloklanıyor ve durma sınıfı beklediği geldi.
- Beş işçinin tamamlanma sırası hiç ölçülmüyor, yalnız hepsinin bittiği ölçülüyor;
  sonuçlar bir kanaldan toplanıp sıralanarak basılıyor.
- Sayaç hiç sıfırlanmazsa `Wait` sonsuza dek bloklanıyor; bu, engellenmeden `select`in
  `default` dalıyla denetlendi ve sızıntı yalnız bir boolean olarak ölçüldü.
- Sayacı sıfırın altına indiren fazladan bir `Done` çağrısı bekleme grubunu paniklettiriyor;
  panik `recover` ile yakalandı, yalnız `panik=true` yazıldı.
- Kilidin sözleşmesi neyin korunacağıydı, bekleme grubunun sözleşmesi kaç `Add`in kaç
  `Done`a karşılık geldiği — ikisi de derleyicinin denetlemediği, yalnız koşturarak
  görülebilen kurallar.

#### Sonraki Adım

Bekleme grubu bir sayı sayıyor ama tek başına hiçbir değeri korumuyor ve hiçbir işlemi
tek seferle sınırlamıyor. İki ders boyunca paylaşılan durumu korumanın tek aracı kilitti;
sıradaki ders bu tekelin doğru olmadığını gösteriyor ve paylaşılan durumun iki ayrı
ilkelini ölçüyor: bir gövdenin kaç goroutine çağırırsa çağırsın yalnız bir kez
çalışmasını garanti eden bir kez çalıştırma ve tek bir değişkeni kilit kullanmadan
koruyan atomik işlemler. Her iki ilkel de kendi dar sözleşmesiyle geliyor — biri yalnız
bir defalık bir kurulumu, öbürü yalnız tek bir değişkeni koruyor — ve bu darlığın nerede
yeterli, nerede yetersiz kaldığı sıradaki dersin kendi sınırlayıcı ölçümü.

### Eşzamanlılık İlkelleri

Source: https://academia.sh/tr/kurslar/go-eszamanlilik/eszamanlilik-ilkelleri

Otuz goroutine aynı ilklendirmeyi çağırıyor ama bir kez çalıştırma sözleşmesi gövdenin tam olarak bir kez koşmasını garanti ediyor. Otuz goroutine, her biri beş yüz artışla, aynı sayacı bir kez atomik işlemlerle bir kez kilitle artırıyor; ikisi de 15000 veriyor. Sınırlayıcı ölçüm, iki ayrı atomik değişkenin (bakiye ve işlem sayısı) her biri kendi başına tutarlı kalırken bir arada okunduklarında tutarsız bir çift verebileceğini kanalla zorlanmış bir sırayla modelliyor: okuyan taraf bakiye=100, işlemSayısı=0 görüyor.

Önceki dersin kapanışı bir sınır çizdi: kilit iki ders boyunca paylaşılan durumu korumanın
tek aracıymış gibi göründü, ama değil. Bu ders aynı korumayı iki ayrı ilkelle kuruyor —
`sync.Once`, bir gövdenin kaç kez çağrılırsa çağrılsın yalnız bir kez koşmasını garanti
eden **bir kez çalıştırma**; ve `sync/atomic`, tek bir değişken üzerindeki işlemleri
kilit kullanmadan güvenli kılan **atomik işlemler**. İkisi de kilidin yaptığı işi
yapıyor gibi görünüyor, ama ikisinin de sözleşmesi kilitten daha dar: kilit herhangi bir
kod parçasını sarabiliyordu, bu iki ilkelin her biri yalnız tek bir dar durumu kapsıyor.

Ortak tanımın üçüncü iddiası bu derste de geçerli kalıyor: durduran şey aracın adı değil,
başka bir goroutine'in attığı bir adım. Bir kez çalıştırmada bu adım ilk çağrının gövdeyi
bitirmesi — bekleyen goroutine'ler kilitteki gibi beklediği geldi sınıfına giriyor. Atomik
işlemde ise hiç bekleme yok: atomik bir çağrı hiçbir goroutine'in bir şey yapmasını
beklemeden, hiç bloklanmadan döner — kendi başına biten bir adım, kimseye bağlı değil.
İkisi de kilidin **tek bir dar biçimini** temsil ediyor, ama biri hâlâ bekletiyor, öbürü
hiç bekletmiyor.

#### Bir Kez Çalıştırma

Bir programın başlangıcında yalnız bir kez çalışması gereken bir adım — bir bağlantı
havuzunu kurmak, bir yapılandırmayı okumak — birden çok goroutine tarafından aynı anda
istenebilir. `sync.Once` bu isteği tek bir çağrıya indiriyor: `Do` yöntemine verilen
işlev, kaç goroutine `Do`yu çağırırsa çağırsın, ömür boyu **tam olarak bir kez** koşuyor;
sonraki her çağrı hiçbir şey yapmadan hemen dönüyor. `sync.Once`in sıfır değeri de tıpkı
kilit gibi kullanıma hazır: `var bir sync.Once` yazmak yeterli, ayrı bir kurulum
gerekmiyor — çünkü `Once`in içinde, ilk çağrının olup olmadığını izleyen bir sayaç ve o
sayacı koruyan kendi kilidi zaten var. `Once`in kendisi, bir önceki dersin ilkelini
kullanarak kurulmuş bir araç.

Aynı `bir` değişkenine ait `Do` çağrılarına **farklı** işlevler verilse bile sonuç
değişmiyor — koşan yalnız **ilk** çağrının işlevi, sonraki çağrılara verilen işlevler hiç
çalışmadan atlanıyor. Bu örnekte otuz goroutine aynı işlevi verdiği için bu ayrım
görünmüyor, ama sözleşme bunu zaten garanti ediyor: `Once`in hafızası "hangi işlev
verildi" değil, yalnız "bir kez çağrıldım mı" sorusuna verilen tek bitlik bir yanıt.
Hangi goroutine'in kendi `Do` çağrısının gövdeyi gerçekten koşturduğu da ölçülmüyor ve
ölçülemez — otuz goroutine aynı anda `Do`yu çağırıyor, hangisinin "kazandığı" zamanlamaya
bağlı; ama hepsi aynı işlevi verdiği için bu belirsizlik, birinci dersin kilitsiz
kurulumunda olduğu gibi, sonucu hiç etkilemiyor.

```go
// main.go — bir kez calistirma, atomik sayac ile kilitli sayacin karsilastirilmasi, cift atomik degiskenin modellenmesi
package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
	fmt.Println("-- bir kez calistirma: N goroutine cagiriyor, govde bir kez kosuyor --")
	var bir sync.Once
	var kacKezKostu int64
	var ilkDeger int64
	const goroutineSayisi = 30
	var grup sync.WaitGroup
	grup.Add(goroutineSayisi)
	for i := 0; i < goroutineSayisi; i++ {
		go func() {
			defer grup.Done()
			bir.Do(func() {
				atomic.AddInt64(&kacKezKostu, 1)
				atomic.StoreInt64(&ilkDeger, 42)
			})
		}()
	}
	grup.Wait()
	fmt.Printf("govde kac kez kostu: %d\n", kacKezKostu)
	fmt.Printf("ilklendirilen deger: %d\n", ilkDeger)

	fmt.Println()
	fmt.Println("-- atomik sayac ile kilitli sayac ayni sonucu veriyor mu --")
	const artisSayisi = 500
	var atomikSayac int64
	var grup2 sync.WaitGroup
	grup2.Add(goroutineSayisi)
	for i := 0; i < goroutineSayisi; i++ {
		go func() {
			defer grup2.Done()
			for j := 0; j < artisSayisi; j++ {
				atomic.AddInt64(&atomikSayac, 1)
			}
		}()
	}
	grup2.Wait()

	var kilit sync.Mutex
	kilitliSayac := 0
	var grup3 sync.WaitGroup
	grup3.Add(goroutineSayisi)
	for i := 0; i < goroutineSayisi; i++ {
		go func() {
			defer grup3.Done()
			for j := 0; j < artisSayisi; j++ {
				kilit.Lock()
				kilitliSayac++
				kilit.Unlock()
			}
		}()
	}
	grup3.Wait()

	fmt.Printf("atomik sayac  : %d\n", atomikSayac)
	fmt.Printf("kilitli sayac : %d\n", kilitliSayac)
	fmt.Printf("ikisi ayni mi : %v\n", atomikSayac == int64(kilitliSayac))

	fmt.Println()
	fmt.Println("-- sinirlayici olcum: iki atomik degiskenin birlikte tutarliligi modelleniyor --")
	var bakiye, islemSayisi int64
	adim1 := make(chan struct{})
	bitti := make(chan [2]int64)

	go func() {
		<-adim1
		okunanBakiye := atomic.LoadInt64(&bakiye)
		okunanIslem := atomic.LoadInt64(&islemSayisi)
		bitti <- [2]int64{okunanBakiye, okunanIslem}
	}()

	atomic.AddInt64(&bakiye, 100)
	adim1 <- struct{}{}
	ara := <-bitti
	atomic.AddInt64(&islemSayisi, 1)

	fmt.Printf("yazan taraf tamamladiginda bakiye=%d islemSayisi=%d\n",
		atomic.LoadInt64(&bakiye), atomic.LoadInt64(&islemSayisi))
	fmt.Printf("okuyan tarafin arada gordugu   bakiye=%d islemSayisi=%d (tutarsiz cift)\n",
		ara[0], ara[1])
}
```

```
-- bir kez calistirma: N goroutine cagiriyor, govde bir kez kosuyor --
govde kac kez kostu: 1
ilklendirilen deger: 42

-- atomik sayac ile kilitli sayac ayni sonucu veriyor mu --
atomik sayac  : 15000
kilitli sayac : 15000
ikisi ayni mi : true

-- sinirlayici olcum: iki atomik degiskenin birlikte tutarliligi modelleniyor --
yazan taraf tamamladiginda bakiye=100 islemSayisi=1
okuyan tarafin arada gordugu   bakiye=100 islemSayisi=0 (tutarsiz cift)
```

**PD9.** Otuz goroutine aynı `bir.Do(...)` çağrısını neredeyse aynı anda yapıyor, ama
`kacKezKostu` yalnız `1`. `sync.Once`in sözleşmesi burada bekleme grubundan farklı bir
şekilde çalışıyor: bekleme grubu bir sayacı **dışarıdan** artırıp azaltan çağrılara
dayanıyordu, `Once` ise kendi içinde bir kez çalıştıysa bir daha hiç çalışmayacağını
**kendisi** kararlaştırıyor. Otuz çağrıdan yalnız biri gövdeyi gerçekten koşturuyor,
geri kalan yirmi dokuzu `Do`nun döndüğü anı — gövde daha önce koşmuşsa hemen, hâlâ
koşuyorsa gövde bitene kadar — bekliyor. Bu da beklediği geldi sınıfının bir başka
düzeneği: bekleyen goroutine'ler kendi başlarına durmuyor, ilk çağrının gövdeyi bitirmesi
onları serbest bırakıyor.

`ilkDeger`in çıktısı bu garantinin ikinci yarısını gösteriyor: yalnız gövdenin **bir kez**
koşması yetmiyor, gövdenin yazdığı değerin **her** goroutine tarafından görülebilmesi de
gerekiyor. Otuz goroutine'den yalnız biri `ilkDeger`e `42` yazıyor, ama `Do`dan dönen
her goroutine — kendi çağrısı gövdeyi koşturmuş olsun ya da olmasın — aynı `42`yi okuyor.
Bu, `sync.Once`in kendi içindeki senkronizasyonun bir sonucu: `Do`dan dönmek, yalnız
"gövde bir yerde koştu" bilgisini değil, gövdenin yazdığı her değeri de beraberinde
taşıyor.

**PD10.** İkinci ölçüm aynı toplamı iki ayrı yoldan üretiyor: `atomikSayac`,
`atomic.AddInt64` ile korunuyor ve hiçbir kilide dokunmuyor; `kilitliSayac`, önceki
dersteki gibi `Lock`/`Unlock` çiftiyle korunuyor. Otuz goroutine, her biri beş yüz artışla,
toplam on beş bin artış uyguluyor ve iki sayaç da tam olarak `15000`e ulaşıyor. Bu eşitlik
birinci dersteki kilitli/kilitsiz karşılaştırmasından farklı: orada kilitsiz kurulum
paylaşılan tek bir belleğe hiç dokunmuyordu, burada `atomikSayac` gerçekten paylaşılan tek
bir bellek konumu ve otuz goroutine ona gerçekten aynı anda yazıyor. Atomik işlem
paylaşılan durum yarışını **koşturmadan değil, güvenli kılarak** dışlıyor — donanımın
sağladığı bölünmez oku-değiştir-yaz işlemiyle, kilit gerekmeden.

**M01/K05 İşletim Sistemi Kavramları**'nda kapanan atomik işlem kavramı burada aynen
geçerli: bir işlemin bölünmez olması, araya başka hiçbir goroutine'in giremeyeceği anlamına
geliyor. Go'nun `sync/atomic` paketi bu bölünmezliği belirli tamsayı ve işaretçi tipleri
için donanım desteğiyle sağlıyor; `AddInt64` çağrısının kendisi hiçbir zaman yarım
kalmıyor, bir goroutine okuma ile yazma arasına asla giremiyor.

Bu güvence bir bedelle geliyor: `sync/atomic`in koruduğu şey yalnız `int32`, `int64`,
`uint32`, `uint64`, işaretçi gibi belirli birkaç tip ve her çağrı bu tiplerden **tek
birinin** üzerinde çalışıyor. Kilit böyle bir sınır tanımıyordu — `Lock`/`Unlock` çifti
arasına bir yapı, bir dilim, bir sözlük, ya da birden fazla değişkeni güncelleyen
keyfi uzunlukta bir kod parçası girebiliyordu. Atomik işlemin dar sözleşmesi tam olarak bu
esnekliğin bir kısmından vazgeçmenin karşılığı: tek bir tamsayıyı korumak için kilide hiç
gerek kalmıyor, ama korunan şey bir tamsayının ötesine geçtiği anda atomik işlem yetersiz
kalıyor.

`sync/atomic` bu derste kullanılan `AddInt64` ve `LoadInt64`in yanında bir de karşılaştırıp
değiştirme çağrısı taşıyor: verilen değişken beklenen değere eşitse yeni değeri yazıyor,
değilse hiçbir şey yapmadan bunu bildiriyor — okuma ile yazma arasına hiçbir goroutine'in
giremeyeceği bir başka bölünmez işlem. Bu ders o çağrıyı koşturmuyor; burada önemli olan
`sync/atomic`in tek bir tamsayı üzerindeki her işlemi (ekleme, okuma, yazma, koşullu
değiştirme) aynı bölünmezlik garantisiyle sunduğu, ama sunduğu şeyin her zaman **tek bir**
değişkenle sınırlı kaldığı.

#### Sınırlayıcı Ölçüm: İki Atomik Değişkeni Birlikte Tutmak

**PD11.** Üçüncü kurulum tek bir değişken yerine iki tanesini — `bakiye` ve
`islemSayisi` — kullanıyor ve ikisi de kendi başına atomik olarak okunup yazılıyor. Ama
aralarında hiçbir birleşik koruma yok: yazan taraf önce `bakiye`yi güncelliyor, sonra
`islemSayisi`ni; bu iki güncelleme arasında kalan bir an, dışarıdan bakan bir goroutine'e
**tutarsız bir çift** gösterebiliyor. Bu boşluk kanalla zorlanan bir sırayla modelleniyor:
okuyan goroutine `adim1` kanalından sinyal bekliyor, `main` önce `bakiye`yi 100 yapıp
sinyali gönderiyor, okuyan goroutine tam bu anda hem `bakiye`yi hem `islemSayisi`ni
okuyor — `bakiye=100` ama `islemSayisi` henüz `0`. `main` ancak bundan sonra
`islemSayisi`ni artırıyor.

Bu, önceki dersteki kilit kapsamı modeliyle aynı ailede bir sınır, ama farklı bir yerde
duruyor: orada sorun kilidin **kapsamının** yanlış çizilmesiydi, burada iki değişkenin
**hiç ortak bir kapsamı** yok — her biri kendi başına atomik, ama ikisi birlikte değil.
`bakiye` okunduğu an tanımlı bir değer taşıyor, `islemSayisi` okunduğu an da tanımlı bir
değer taşıyor; okuma sonucu **yanlış** değil, yalnızca iki okumanın arasına başka bir
goroutine'in bir yazması girebiliyor ve bu, tek başına doğru olan iki değeri bir arada
yanlış bir hikâyeye dönüştürebiliyor. Atomik işlem tek bir değişkeni korumak için var;
birden çok değişkenin **birlikte** tutarlı kalması gerekiyorsa atomik işlem yetmiyor, o
korumayı yeniden bir kilit üstleniyor.

Bu sınırın kendisi bir tasarım hatası değil, önceki dersteki kilit kapsamı örneğinin bir
genellemesi. Orada sorun tek bir sayacın oku-hesapla-yaz üçlüsünün kilidin dışına
taşmasıydı; burada iki ayrı sayacın **ikisi birden** tek bir kapsam altında değil. İkisi
de aynı soruyu soruyor: korunması gereken işlem nerede başlayıp nerede bitiyor? `bakiye` ve
`islemSayisi` ayrı ayrı atomik olduğu için bu soru kaynağa bakılarak fark edilmiyor —
her iki değişken de kendi satırında doğru görünüyor, aralarındaki ilişkinin korunmadığı
yalnızca iki değişkeni **birlikte** okumaya çalışan bir kod parçası yazılıp koşturulduğunda
ortaya çıkıyor.

#### Kilit, Bir Kez Çalıştırma, Atomik İşlem

Üç ders boyunca ölçülen üç araç aynı soruya üç ayrı dar yanıt veriyor. Kilit "hangi kod
parçası aynı anda tek bir goroutine tarafından çalışsın?" sorusuna yanıt veriyor ve bu
kapsamı `Lock`/`Unlock` çiftiyle kendi çiziyor — çizim yanlış olursa (birinci dersin
sınırlayıcı ölçümü) kilit bunu fark etmiyor. Bir kez çalıştırma "bu gövde daha önce koştu
mu?" sorusuna yanıt veriyor ve bu soruyu sonsuza dek hatırlıyor; ikinci bir `Do` çağrısı
gövdeyi bir daha hiç çalıştırmıyor, çünkü soru zaten bir kez yanıtlandı. Atomik işlem "bu
tek değişkene aynı anda kaç goroutine dokunabilir?" sorusuna yanıt veriyor ve yanıtı
donanım düzeyinde garanti ediyor, ama soruyu yalnız **tek bir** değişken için soruyor.

Üçü de paylaşılan durumu koruyor, ama üçünün kapsamı birbirinin yerine geçmiyor: bir kez
çalıştırmayla bir sayacı sürekli korumak, ya da atomik işlemle birden fazla adımı tek bir
işlem gibi göstermek mümkün değil. Kod tabanında hangi ilkelin seçildiği, korunması gereken
şeyin doğasını — bir olay mı, tek bir değer mi, yoksa keyfi bir kod parçası mı — açığa
çıkarıyor. Bu üç dersin ortak sonucu bu: paylaşılan durumu korumanın tek bir doğru yolu
yok, korunacak şeyin şekline göre değişen üç ayrı dar sözleşme var.

Bu üç dar sözleşme aynı zamanda birbirine iç içe geçebiliyor. `sync.Once`in kendisi bir
kilitle kuruldu; ikinci dersin `KorumaliSayac`ı gibi bir tip, kendi kilidini taşırken
aynı zamanda bir `sync.Once` alanı taşıyıp yalnız ilk erişimde bir alanı ilklendirebilir.
Atomik işlem de kilidin **yerini almıyor**, kilidin bir yerine geçebileceği en dar
durumları kilitsiz bırakıyor. Üçü de aynı ölçü ekseninin birer görünümü: bir goroutine'in
paylaşılan bir duruma nasıl eriştiği, o durumun ne kadarını, ne kadar süreyle koruma
altında tuttuğuyla ölçülüyor.

#### Özet

- `sync.Once`, `Do`ya verilen gövdenin kaç goroutine çağırırsa çağırsın tam olarak bir kez
  koşmasını garanti ediyor; otuz çağrıdan yalnız biri gövdeyi koşturdu.
- Bekleyen goroutine'ler `Once`in gövdesi bitene kadar bloklanıyor — beklediği geldi
  sınıfının kilit ve bekleme grubundan farklı bir düzeneği.
- `sync/atomic`, tek bir değişken üzerindeki işlemleri donanımın bölünmez oku-değiştir-yaz
  desteğiyle koruyor; otuz goroutine on beş bin artış uyguladı ve atomik sayaç kilitli
  sayaçla aynı sonucu (`15000`) verdi.
- İki ayrı atomik değişken, her biri kendi başına tutarlı olsa da, birlikte okunduğunda
  tutarsız bir çift verebiliyor; bu, kanalla zorlanmış bir sırayla modellendi, koşturulmadı.
- Atomik işlem tek bir değişkeni korur; birden fazla değişkenin birlikte tutarlılığı
  gerekiyorsa kilit kapsamı hâlâ gereken araç.
- Üç ders boyunca ölçülen kilit, bekleme grubu, bir kez çalıştırma ve atomik işlem aynı
  ölçü ekseninin dört ayrı görünümü: her biri paylaşılan durumun ne kadarını, ne kadar
  süreyle koruma altında tuttuğuyla tanımlanıyor.

#### Sonraki Adım

Üç ders boyunca paylaşılan durum kilitle, bekleme grubuyla ve şimdi bir kez çalıştırma ve
atomik işlemlerle korundu — ama her ölçüm korumanın **var olduğunu** baştan biliyordu,
çünkü kaynağı biz yazdık. Bu üç dersin hiçbirinde gerçek bir yarış koşulu koşturulmadı:
birinci ders bunu ayrık bellekle ve zorlanmış bir modelle, bu ders de zorlanmış bir
modelle dışladı. Gerçek bir kaynak tabanında korumanın unutulup unutulmadığını, kaynağı
okuyarak değil, kim söylüyor? Kursun son dersi bu soruyu, dilin kendi dağıtımıyla gelen
yarış koşulu saptayıcısına soruyor: korumasız bir paylaşım, kilitli bir paylaşım ve kanal
üzerinden bir aktarım aynı saptayıcıdan geçiriliyor, saptayıcının verdiği yanıt (buldu mu,
bulamadı mı) okunuyor ve saptayıcının kendi sınırı — hiç çalışmayan bir yoldaki yarışı
görememesi — kursun son ölçümü olarak ortaya çıkıyor.

### Yarış Koşulu Saptayıcı

Source: https://academia.sh/tr/kurslar/go-eszamanlilik/yaris-kosulu-saptayici

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.

Üç 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.

```go
// 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
}
```

`SaptayiciBulduMu`nun 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

```go
// 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ı `sayac`a 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.

`kanalli`nin 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. `kilitli`nin 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 `Wait`in 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.

