---
title: 'Kanallarla Mesajlaşma'
source: 'https://academia.sh/tr/kurslar/rust-eszamanlilik/kanallarla-mesajlasma'
course: 'Eşzamanlılık ve Asenkron Rust'
language: tr
updated: '2026-09-13T05:09:28+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Bir kanaldan gönderilen değerin sahipliğinin alıcıya taşındığını ve gönderende kalmadığını gösterir'
    - 'Kanaldan haber bekleyen tarafın başkası bitince, haberi kuran tarafın kendi başına ilerlediğini ölçer'
    - 'Beklenen sayıda kayıt gelmediğinde eksikliğin nasıl ölçüldüğünü gösterir'
---

# Kanallarla Mesajlaşma

Bir kanaldan haber bekleyen taraf başkası bitince ilerler, haberi kuran ve gönderen taraf kendi başına ilerler; gönderilen değerin sahipliği kanala taşınır ve gönderen tarafta bir daha kullanılamaz.

Önceki ders bir sinyal kanalını yalnızca bir uyandırma aracı olarak kullandı: gönderilen değer
boştu, önemli olan yalnızca gönderimin gerçekleşip gerçekleşmediğiydi. Bu ders aynı düzeneği
bir yükle çalıştırıyor. Kanaldan geçen değer artık boş değil; bir veri taşıyor ve o verinin
kime ait olduğu sorusu, ilerleme sorusuyla aynı derste birlikte ölçülüyor.

Kanal kavramının kendisi bu kursta kurulmuyor. Go'nun Eşzamanlılık kursu kanalı ayrıntılı
biçimde ele aldı, ama orada ölçülen kanalın bir **durma** düzeneği olarak kullanılmasıydı —
bir goroutine ne zaman ve neden durur sorusunun bir parçasıydı. Bu derste aynı düzenek bir
**ilerleme** aracı olarak ölçülüyor: kanal burada bir birimi bitirmiyor, bir birimi
başlatıyor ya da devam ettiriyor. Kanalın kendi işleyişi — tamponlu ya da tamponsuz olması,
kapanma davranışı, çok üretici tek tüketici düzeni — o kursta kapandı ve burada
tekrarlanmıyor; bu ders yalnızca kanalın ilerleme sınıflarına nasıl karşılık geldiğini
soruyor.

## Sahiplik Taşıyan Kanal

Sahiplik ve Bellek Modeli kursu bir değerin taşınmasının ne anlama geldiğini kurdu: bir değer
bir bağlamadan diğerine taşındığında, ilk bağlama artık geçerli değildir ve değere yalnızca
yeni bağlama üzerinden erişilebilir. Bir kanaldan gönderim de bu kuralın bir örneğidir —
`send` çağrısı değeri alır ve kanala taşır; gönderen taraf o değere bir daha dokunamaz.

Bunun sonucu, alıcı tarafın değeri **kilitsiz** biçimde değiştirebilmesidir. Aşağıdaki
program bir listeyi bir iş parçacığından diğerine gönderiyor; alan taraf listeyi büyütüyor,
ama bunu yaparken hiçbir kilide ihtiyaç duymuyor, çünkü listenin o andaki tek sahibi kendisi.

Bu, iki iş parçacığının aynı öbek belleğine erişebildiği bir durumla karıştırılmamalı.
`liste` değeri veren iş parçacığında kurulduğunda öbekte bir yer kaplıyor; `send` çağrısı bu
yerin adresini değil, o yerin **sahipliğini** taşıyor. Alan taraf `liste.push` çağırdığında
büyüme gerekiyorsa Rust'ın büyüyen dizi tipi öbekte yeni bir yer ayırıp eski içeriği oraya
kopyalıyor — ama bu işlem sırasında ikinci bir tarafın aynı belleğe erişme ihtimali hiç yok,
çünkü veren iş parçacığı zaten birleştirilmiş ve elinde `liste` adlı bir bağlama kalmamış
durumda.

```rust
// tasima.rs — kanaldan gelen deger tasindigi icin kilitsiz mutasyona aciktir.

use std::sync::mpsc::channel;
use std::thread;

fn main() {
    let (gonder, al) = channel::<Vec<i32>>();

    let veren = thread::spawn(move || {
        let liste = vec![1, 2, 3];
        gonder.send(liste).unwrap();
        // liste burada artik kullanilamaz: sahiplik kanala tasindi.
    });
    veren.join().unwrap();

    // --- alan taraf: kilit gerekmeden dogrudan buyutuyor, cunku tek sahibi kendisi ---
    let mut liste = al.recv().unwrap();
    liste.push(4);
    liste.push(5);

    println!("alinan listenin uzunlugu: {}", liste.len());
    println!("kilitsiz buyutuldu, cunku baska hicbir yerde bu deger paylasilmiyor");
}
```

```
alinan listenin uzunlugu: 5
kilitsiz buyutuldu, cunku baska hicbir yerde bu deger paylasilmiyor
```

**IP2.** `liste` değişkeni `send` çağrısına verildikten sonra kaynakta bir daha görünmüyor;
görünseydi kaynak derlenmezdi, çünkü taşınmış bir değere erişim Rust'ın sahiplik kuralının
ihlalidir. Bu kısıt bu derste bir hata olarak koşturulmuyor — yalnızca düzyazıda tarif
ediliyor — ve yanına, kısıta uyan kabul edilmiş tasarım konuyor: veren taraf değeri kurup
gönderiyor, alan taraf devralıp büyütüyor, ikisi arasında hiçbir an ortak bir referans
bulunmuyor. Kilidi Muteks ve Okuma/Yazma Kilidi dersi anlatacak; oradaki paylaşım burada
yoktur, çünkü paylaşılan bir şey değil, tamamen el değiştiren bir şey var.

Bu ayrım kanalın kendi türüne de yansıyor: `channel::<Vec<i32>>()` yazımı, kanalın yalnızca
`Vec<i32>` **tipinde** değerler taşıyacağını, bu değerlerin bir referans ya da bir kopya
değil, tam ve tek bir sahiplik olarak geçeceğini söylüyor. Alan taraf `al.recv()` çağrısından
dönen değeri aldığında, o değerin kaynaktaki tek nüshası artık kendi elindedir; `liste.push`
çağrısının hiçbir eşzamanlılık ilkeline ihtiyaç duymaması bu yüzdendir — büyütülen liste,
büyütüldüğü anda başka hiçbir bağlamanın erişebileceği bir yerde durmuyor. Bu, kanalın
neden bir dışlama kilidinden farklı bir araç olduğunu da açıklıyor: kilit paylaşılan bir
değere sırayla erişim sağlar, kanal ise değeri hiç paylaşmadan bütünüyle devrediyor.

## Veren Kendi Başına, Bekleyen Başkası Bitince

Kanaldan geçen değerin sahiplik hikâyesi bir yana, aynı gönderim aynı zamanda bir ilerleme
sorusu taşıyor. Aşağıdaki ölçüm bunu iki tarafla ayrı ayrı gösteriyor: veren taraf üç ayrı
ileti kurup gönderiyor, hiçbir şeyi beklemeden; bekleyen taraf her ileti için ayrı bir alım
çağrısı yapıyor ve bu çağrı, karşı taraf göndermeden dönmüyor.

Bu çift, önceki dersin ölçüm çekirdeğindeki `iplik-isaret-veren` ile `iplik-kanal-bekleyen`
satırlarının aynısı — orada sinyal boş bir değerdi (`()`), burada aynı ilişki gerçek bir yük
taşıyan üç ayrı iletiyle tekrarlanıyor. Sınıflandırma değişmiyor: sinyali kuran taraf orada
da burada da kendi başına, sinyali bekleyen taraf orada da burada da başkası bitince
ilerliyor. Değişen, kanaldan geçenin ne taşıdığı.

Ölçümde iki ayrı kanal birlikte çalışıyor. Birincisi `ileti_gonder`/`ileti_al` çifti: asıl
yük olan `Ileti` değerlerini taşıyor, her biri bir içerik dizgesi ve bir sıra numarası
taşıyan bir yapı. İkincisi, önceki dersten tanıdık olan rapor kanalı: her iki tarafın da
kendi ilerlemesini bir deftere yazması için kullanılıyor ve bu defter, sırası ölçülmeden
önce sıralanıyor. İki kanalın ayrılması bilinçli — asıl mesajlaşmayı ölçen kanal ile o
ölçümü doğrulamak için kullanılan rapor kanalı karışırsa, hangi kaydın gerçek işin parçası
hangisinin yalnızca gözlem olduğu belirsizleşir.

Kodda hem `rapor_gonder.clone()` hem `Ileti` değerinin taşınması bir arada görünüyor ve
ikisi farklı şeyler. Kanalın gönderen ucu klonlanabilir bir tiptir: `rapor_gonder.clone()`
çağrısı, aynı kanala yazabilen ikinci bir tutamaç üretir; iki tutamaç veri paylaşmaz, yalnızca
aynı hedefe erişim hakkını paylaşır. Her iş parçacığının kendi tutamacına sahip olması,
birden fazla göndericinin aynı kanala eşzamanlı yazabilmesini sağlıyor — bu, `send()`
çağrısının kendisinin dışlama kilidi gerektirmemesinin nedeni. `Ileti` değeri ise hiç
klonlanmıyor: `send` çağrısına doğrudan taşınıyor, çünkü onun tam olarak **tek** bir sahibi
olması gerekiyor. Bir tutamacın çoğaltılabilir olması ile bir değerin tek sahipli kalması,
aynı programda yan yana duran iki ayrı kural.

Ölçümdeki `Ileti` yapısı iki alan taşıyor: `icerik` bir dizge, `sira` bir tam sayı. İkisinin
birlikte gönderilmesi bilinçli bir seçim — kanal yalnızca tek bir tamsayı ya da tek bir
dizge değil, keyfi karmaşıklıkta bir değeri de aynı sahiplik kuralıyla taşıyabildiğini
gösteriyor. `sira` alanı ayrıca ölçümün kendisine hizmet ediyor: bekleyen tarafın topladığı
`toplam_sira`, üç iletinin de eksiksiz ve doğru içerikle geldiğini, yalnızca sayılarının
değil değerlerinin de doğru taşındığını kanıtlıyor.

```rust
// kanal.rs — kanaldan haber bekleyen baskasi bitince ilerler, haberi veren kendi basina ilerler.

use std::sync::mpsc::channel;
use std::thread;

struct Ileti {
    icerik: String,
    sira: u32,
}

fn main() {
    let (rapor_gonder, rapor_al) = channel::<String>();

    // --- veren taraf: kendi basina, kimseyi beklemeden ileti kuruyor ve gonderiyor ---
    let (ileti_gonder, ileti_al) = channel::<Ileti>();
    let rg = rapor_gonder.clone();
    let veren = thread::spawn(move || {
        for sira in 1..=3 {
            let deger = Ileti {
                icerik: format!("kayit-{}", sira),
                sira,
            };
            ileti_gonder.send(deger).unwrap();
            rg.send(format!("verici-gonderdi-{}", sira)).unwrap();
        }
        // deger burada artik yok: sahiplik gonder cagrisiyla kanala tasindi.
    });

    // --- bekleyen taraf: baskasi bitince ilerliyor, her ileti icin ayri recv() ---
    let rg = rapor_gonder.clone();
    let bekleyen = thread::spawn(move || {
        let mut toplam_sira = 0u32;
        for _ in 1..=3 {
            let ileti = ileti_al.recv().unwrap();
            toplam_sira += ileti.sira;
            rg.send(format!("bekleyen-aldi-{}", ileti.icerik)).unwrap();
        }
        toplam_sira
    });

    veren.join().unwrap();
    let toplam = bekleyen.join().unwrap();

    let mut kayitlar: Vec<String> = Vec::new();
    for _ in 0..6 {
        kayitlar.push(rapor_al.recv().unwrap());
    }
    kayitlar.sort();

    println!("tam senaryo — toplanan sira: {}", toplam);
    println!("kayitlar ({}):", kayitlar.len());
    for k in &kayitlar {
        println!("  {}", k);
    }

    // --- eksik senaryo: beklenen 3 verici var ama yalniz 2'si baslatildi ---
    let (rapor_gonder2, rapor_al2) = channel::<String>();
    let (ileti_gonder2, ileti_al2) = channel::<Ileti>();
    let rg2 = rapor_gonder2.clone();
    let eksik_veren = thread::spawn(move || {
        for sira in 1..=2 {
            let deger = Ileti {
                icerik: format!("eksik-kayit-{}", sira),
                sira,
            };
            ileti_gonder2.send(deger).unwrap();
            rg2.send(format!("eksik-verici-gonderdi-{}", sira)).unwrap();
        }
    });
    eksik_veren.join().unwrap();
    drop(rapor_gonder2);

    let mut eksik_kayitlar: Vec<String> = Vec::new();
    while let Ok(k) = rapor_al2.recv() {
        eksik_kayitlar.push(k);
    }
    eksik_kayitlar.sort();

    let mut alinan_iletiler: Vec<String> = Vec::new();
    while let Ok(ileti) = ileti_al2.try_recv() {
        alinan_iletiler.push(ileti.icerik);
    }
    alinan_iletiler.sort();

    let beklenen = 3;
    println!();
    println!("eksik senaryo — beklenen: {}, gelen: {}", beklenen, alinan_iletiler.len());
    for k in &alinan_iletiler {
        println!("  {}", k);
    }
    println!("eksik kayit sayisi: {}", beklenen - alinan_iletiler.len());
}
```

```
tam senaryo — toplanan sira: 6
kayitlar (6):
  bekleyen-aldi-kayit-1
  bekleyen-aldi-kayit-2
  bekleyen-aldi-kayit-3
  verici-gonderdi-1
  verici-gonderdi-2
  verici-gonderdi-3

eksik senaryo — beklenen: 3, gelen: 2
  eksik-kayit-1
  eksik-kayit-2
eksik kayit sayisi: 1
```

**IP3.** Tam senaryoda `veren` iş parçacığı üç ileti kurup gönderiyor ve hiçbir kaydı
başkasının bitmesini beklemeden yazıyor — kendi başına sınıfına giriyor. `bekleyen` iş
parçacığı ise her `recv` çağrısında karşı tarafın bir sonraki göndermesini bekliyor —
başkası bitince sınıfına giriyor. Toplanan sıra `1 + 2 + 3 = 6`; bu değer, üç iletinin de
sırasıyla ve eksiksiz alındığının doğrulamasıdır ve üç ayrı koşumda birebir aynı çıkıyor.

`toplam_sira` değerinin kendisi de önceki dersin bir kuralını sürdürüyor: `bekleyen` iş
parçacığının kapanışı bu sayıyı döndürüyor ve `bekleyen.join()` çağrısı bu döndürülen değeri
çağıran tarafa taşıyor. İş Parçacığı Oluşturma dersinde kurulan kural burada da geçerli —
kapanışın ürettiği değer, sahiplik ve Bellek Modeli kursunun taşıma kuralıyla birleştirme
çağrısına geçiyor ve çağıran taraf o değeri birleştirme bitmeden okuyamıyor. Burada fark
edilecek olan, bu taşımanın hem `Ileti` yapıları için hem de basit bir `u32` toplamı için
aynı yolu izlemesi: kanal ve birleştirme, farklı veri biçimlerinde aynı sahiplik kuralını
uyguluyor.

## Sınırlayıcı Ölçüm

Bu derste ölçülen ilerleme yalnızca iki uçlu: ya bir ileti gelir ve bekleyen taraf devam
eder, ya da hiç gelmez ve bekleyen taraf orada kalır. Kaç iletinin, hangi hızda geldiği bu
kursta hiç ölçülmüyor — yalnızca üç iletinin de gelip gelmediği. Bekleyen taraf **süresiz**
bekler. `recv` çağrısı, karşı taraftan bir ileti gelene kadar
döner. Eğer beklenen sayıda gönderim hiç gerçekleşmezse, kayıt hiç oluşmaz — ve gerçek bir
programda bekleyen taraf sonsuza kadar bloke kalırdı. Bu dersin ikinci ölçümü, bu durumu bir
kilitlenmeye girmeden göstermek için beklenen ve gerçekleşen sayıyı ayrı ayrı hesaplıyor:
"eksik senaryo"da üç ileti bekleniyordu, ama veren taraf yalnızca iki tanesini gönderip
bitirdi. Program üçüncü iletiyi sonsuza kadar beklemek yerine, göndericinin ve rapor
kanalının kapandığını (`drop(rapor_gonder2)`) algılıyor, gelen kayıtları topluyor ve eksik
sayıyı **1** olarak raporluyor.

Bu algılama şansa bırakılmıyor: `drop(rapor_gonder2)` çağrısı, kanalın gönderen ucunun tek
kalan kopyasını açıkça bırakıyor. Sahiplik ve Bellek Modeli kursu bir değerin bırakılmasının
ömrünün bittiği noktalardan biri olduğunu kurdu; burada aynı düzenek bir kanalın **kapanma**
sinyaline dönüşüyor. Rust'ın kanal uygulaması, gönderen uçların hepsi bırakıldığında alıcı
tarafın bir sonraki `recv` çağrısını hata olarak sonlandırır — programda kullanılan
`while let Ok(k) = ...` yazımı bu hatayı örtük biçimde döngü sonu sayıyor. Eğer
`rapor_gonder2` bırakılmasaydı, döngü kanalın hiçbir zaman kapanmayacağını varsayar ve
sonsuza kadar beklerdi; bırakma burada yalnızca bir bellek temizliği değil, alıcıya
"artık gelecek başka bir şey yok" diyen bir sinyal.

Bu, gerçek bir uygulamanın kendi süresizce bekleme riskini nasıl yönettiğinin bir örneği
değil — o, İptal ve Zaman Aşımı dersinin konusu olan zaman aşımı düzeneğine yakın bir
soru ve orada ayrı ele alınacak. Burada ölçülen yalnızca şu: beklenen sayı ile gelen sayı
arasındaki fark, hangi kaydın **eksik** olduğunu (bu örnekte üçüncü ileti) doğrudan
gösteriyor, üçüncü iletinin ne zaman geleceğini değil — çünkü hiç gelmedi.

Eksik senaryonun kalan iletileri toplarken kullanılan çağrı da bu sınırı gösteriyor. Program
`eksik_veren` iş parçacığını birleştirdikten **sonra** iletileri topluyor; bu noktada
gönderilecek her şey zaten gönderilmiş durumda, bu yüzden bekleyen bir çağrıya gerek yok —
kanalda ne varsa hemen okunup toplanabiliyor. Bu sıralama belirlenimciliği koruyor: iletiler
her koşumda aynı sırayla, aynı sayıda toplanıyor, çünkü toplama işlemi hiçbir canlı
göndericinin daha ileti gönderip göndermeyeceğine bağlı değil. Beklenen sayının üçüncü
iletisi bu yüzden hiçbir zaman gelmiyor ve program onu beklemeye çalışmıyor; yalnızca
beklenen ile gelen sayıyı karşılaştırıp farkı yazdırıyor.

## Özet

- Kanaldan gönderilen bir değerin sahipliği alıcıya taşınır; gönderen taraf o değere bir daha
  erişemez ve alan taraf hiçbir kilide ihtiyaç duymadan değeri değiştirebilir.
- Haberi kuran ve gönderen taraf kendi başına ilerler; kanaldan haber bekleyen taraf başkası
  bitince ilerler — bu ayrım her `recv` çağrısı için ayrı ayrı geçerlidir.
- Tam senaryoda üç iletinin toplam sırası (6) üç ayrı koşumda birebir aynı çıkar; bu,
  gönderim ile alımın eksiksiz eşleştiğinin ölçüsüdür.
- Beklenen sayıda gönderim gerçekleşmezse bekleyen taraf süresiz bekler; eksik kayıt,
  beklenen ile gelen sayı arasındaki farkla ölçülür, asla bir bekleme süresiyle değil.
- Sınır çizgisi Muteks ve Okuma/Yazma Kilidi dersine bırakılıyor: burada paylaşılan bir
  değer yok, yalnızca el değiştiren bir değer var.

## Sonraki Adım

Bu derste bir değerin bir iş parçacığından diğerine geçebilmesi iki örnekle gösterildi:
`Vec<i32>` ve `Ileti`. İkisi de sorunsuzca geçti, ama bu geçiş şimdiye kadar hiç
sorgulanmadı — sanki her tip bir kanaldan geçebilirmiş gibi davranıldı. Aslında bu izin
tipin kendisinde yazılı bir koşuldur ve her tip bu koşulu sağlamaz. Sıradaki ders bu koşulun
nerede tutulduğunu soruyor: hangi tipler bir iş parçacığı sınırını geçebilir, hangileri
geçemez ve bu, derleme zamanında nasıl bilinir.
