---
title: 'Tamponlu ve Tamponsuz Kanallar'
source: 'https://academia.sh/tr/kurslar/go-eszamanlilik/tamponlu-ve-tamponsuz-kanallar'
course: Eşzamanlılık
language: tr
updated: '2026-08-23T16:55:15+00:00'
license: 'CC BY-SA 4.0'
---

# 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.
