---
title: 'Kanal Kapatma ve Aralık'
source: 'https://academia.sh/tr/kurslar/go-eszamanlilik/kanal-kapatma-ve-aralik'
course: Eşzamanlılık
language: tr
updated: '2026-08-23T16:55:15+00:00'
license: 'CC BY-SA 4.0'
---

# Kanal Kapatma ve Aralık

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 B payının konusu; 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 B
payının ilk konusu.

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ığı.
