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

# 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 C payının konusu; 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ı — B payının konusu; 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.
