---
title: 'Eşzamanlılık ve Asenkron Rust'
source: 'https://academia.sh/tr/kurslar/rust-eszamanlilik'
language: tr
updated: '2026-09-13T05:09:27+00:00'
license: 'CC BY-SA 4.0'
lessons: 10
outcomes:
    - 'Tip sisteminin veri yarışını nasıl engellediğini açıklar'
    - 'Bir işin ilerlemek için neye ihtiyaç duyduğunu koşturarak ölçer'
    - 'İş parçacığı ve eşzamansız görev modellerini karşılaştırır'
    - 'Paylaşılan durumu güvenli ilkellerle korur'
---

# Eşzamanlılık ve Asenkron Rust

Aynı anda birden çok işin yürüdüğü bir programda asıl soru hangi işin ne zaman çalıştığı değil, ilerlemek için neye ihtiyaç duyduğudur: kurs her düzeneği tek bir soruyla ölçer — bu iş kendi başına mı ilerliyor, yoklanınca mı, başkası bitince mi, yoksa hiç mi? İş parçacıkları, kanallar, kilitler, gelecekler ve yürütücüler aynı çerçeveden geçirilir.

## İş Parçacıkları

Bir işin ayrı bir yürütme birimine taşınması ve bitişinin beklenmesi, sahipliğin aktarılmasıyla kurulan mesajlaşma, hangi tiplerin sınırı geçebileceğinin tipte yazılı olması, paylaşılan durumun kilitle korunması, ve kilitsiz bir sayacın bellek sıralamasıyla verdiği güvence.

### İş Parçacığı Oluşturma

Source: https://academia.sh/tr/kurslar/rust-eszamanlilik/is-parcacigi-olusturma

Kursun açılış dersi ölçüm çekirdeğini tam koşturur: on üç birimin 5'i kendi başına, 3'ü yoklanınca, 3'ü başkası bitince ilerledi, 2'si hiç ilerlemedi; iş parçacığı başlatma ve birleştirme bu dağılımın kendi başına ve başkası bitince sınıflarını kuruyor.

Trait'ler, Genellikler ve Hata Yönetimi kursu on beş ders boyunca kaynakta yazılan bir gövdenin
kaç kez koştuğunu ölçtü ve kapanışında bu soruyu bırakmadığını, yönünü değiştireceğini
söyledi. O kapanış cümlesi sıradaki sorunun "hangi iş parçacığının koşturduğu" olacağını
öngörmüştü — ama bu, JVM, Eşzamanlılık ve Başarım kursunun kendi sorusudur ve orada zaten
yanıtlandı. Bu kurs başka bir soru soruyor. Bir iş, kaynakta yazılı olsa bile, ilerlemek için
bir şeye ihtiyaç duyabilir: bir yoklayıcıya, başka bir birimin bitmesine, ya da hiçbir şeye.
Kurs boyunca sorulan tek soru şu: **bu iş ilerlemek için neye ihtiyaç duyuyor?**

Bu, deponun beşinci eşzamanlılık kursudur. Önceki dördü aynı alanı farklı sorularla dolaştı:
İşletim Sistemi Kavramları kursu kaç ayrı serpiştirmenin mümkün olduğunu saydı, Python'ın
Eşzamanlılık ve Başarım kursu bir tik modelinde hangi adımların örtüştüğünü ölçtü, JVM,
Eşzamanlılık ve Başarım kursu bir adımı hangi iş parçacığının koşturduğunu izledi, Go'nun
Eşzamanlılık kursu bir goroutine'in nasıl duracağını sınıflandırdı. Bu kurs hiçbirinin adını
yeniden kullanmıyor: kilit, kanal, iş parçacığı, veri yarışı ve zamanlayıcı kavramları zaten
kuruldu ve burada yeniden tanımlanmıyor. İlgili bir kavrama değinilecekse önce hangi kursta
kurulduğu adıyla anılacak, orada ne ölçüldüğü tek cümleyle söylenecek, sonra bu kursun kendi
sorusuna dönülecek.

#### Dört Sınıf

Sorunun dört yanıtı var ve kurs boyunca bu dört ad değişmiyor.

| Sınıf | Tanım |
|---|---|
| kendi başına | Başlatıldıktan sonra kimseye ihtiyacı yok, kendi ilerliyor |
| yoklanınca | Kendiliğinden ilerlemiyor; bir yoklayıcı onu ilerletiyor |
| başkası bitince | Bir başka birimin bitmesini bekliyor |
| hiç | Kaynakta yazılı, ama hiç ilerlemedi |

Bu dörtlü JVM, Eşzamanlılık ve Başarım kursunun sınıflandırmasıyla karışmamalı: orada ölçülen
bir adımı **hangi** iş parçacığının koşturduğuydu, burada ölçülen bir birimin ilerlemek için
**neye ihtiyaç duyduğu**. Go'nun Eşzamanlılık kursuyla da karışmamalı: orada ölçülen bir
birimin nasıl **duracağı**ydı (bitiş), burada ölçülen nasıl **ilerleyeceği** (başlangıç ve
devam). İşletim Sistemi Kavramları kursunun serpiştirme sayımı ve Python'ın Eşzamanlılık ve
Başarım kursunun tik modeli de bu dörtlüyle karışmıyor: ikisi de bir adımın **ne zaman**
çalıştığını sayıyordu, bu kurs ise bir birimin ilerlemesi için **ne** gerektiğini soruyor —
zaman bilgisi hiçbir yerde ölçülmüyor. Bu dersin konusu kendi başına ve başkası bitince
sınıfları: bir iş parçacığı başlatıldığında kendi başına mı ilerliyor, yoksa onu birleştirmek
için bekleyen taraf başkası bitince mi ilerliyor.

#### İş Parçacığı Başlatma ve Birleştirme

İşletim Sistemi Kavramları kursu bir iş parçacığının ne olduğunu ve zamanlayıcının onu nasıl
çalıştırdığını kurdu; bu kurs o kavramı yeniden anlatmıyor. Rust'ta bir iş parçacığı
başlatmak, bir kapanışı ayrı bir yürütme biriminin çalıştıracağı biçimde teslim etmektir.
Başlatma çağrısı bir tutamaç döndürür; bu tutamaç üzerinde birleştirme çağrıldığında, çağıran
taraf o iş parçacığının bitmesini bekler ve iş parçacığının döndürdüğü değere erişir.

Bu iki işlemin ilerleme koşulları farklıdır. Başlatılan iş parçacığının kendisi, çalışmaya
başladığı andan itibaren kimseye ihtiyaç duymaz — zamanlayıcı ona bir işlemci payı verdiği
sürece kendi başına ilerler. Buna karşılık birleştirme çağrısını yapan taraf, o iş parçacığı
bitene kadar ilerleyemez; bu, başkası bitince sınıfının en doğrudan örneğidir. Aynı ayrım,
sinyal bekleyen bir iş parçacığı için de geçerli: bir kanaldan haber beklemek, birleştirme
çağırmaktan farklı bir düzenektir ama aynı sınıfa girer, çünkü ikisi de kendi dışında bir
olayın gerçekleşmesini bekler.

Birleştirme çağrısının kendisi bir değer döndürür: başlatılan kapanışın döndürdüğü değer,
tutamaç üzerinden geri gelir. Bu, Sahiplik ve Bellek Modeli kursunda kurulan taşıma
kuralının doğrudan bir uzantısıdır — kapanışın yakaladığı her değer o iş parçacığına
taşınıyor, kapanış kendi ürettiği değeri döndürdüğünde bu değer de birleştirme çağrısına
taşınıyor. Çağıran taraf o değere birleştirme bitmeden erişemez; erişebilseydi, iş parçacığı
henüz üretmemiş olabileceği bir değeri okumaya çalışırdı. Birleştirmenin başkası bitince
sınıfına girmesi bu yüzden bir uygulama ayrıntısı değil, sahiplik kuralının zorunlu
sonucudur.

#### `hiç` Sınıfı: Kaynakta Yazılı, Hiç Çalışmadı

İki birim bu koşumda hiç ilerlemedi ve ikisi de kaynakta gayet açık biçimde duruyor.
Birincisi bir kapanış: `_hic_baslatilmayan` adıyla bağlanıyor, rapor kanalına bir kayıt
göndermek üzere yazılıyor, ama hiçbir yerde çağrılmıyor ya da bir iş parçacığına teslim
edilmiyor. Rust'ta bir kapanış, çağrılmadığı sürece yalnızca bir değerdir — içindeki gövde
derlenir, ama koşum sırasında bir kez bile tetiklenmez. İkincisi bir görev:
`gorev-hic-yoklanmayan` adlı `BirKezDuraklar` değeri kuruluyor, ama `kostur` işlevine hiç
verilmiyor; bir gelecek de, kapanış gibi, yoklanmadığı sürece yalnızca bellekte duran bir
değerdir.

Bu ikisinin ortak noktası, kaynaktaki varlıklarının koşumdaki etkileriyle aynı şey olmaması.
`siniflar` dizisindeki on üç satırın hepsi kaynakta yazılı; rapor tablosundaki "kayit var mi"
sütunu yalnızca ikisi için "hayir" diyor. Bu, kaynak kodunun ölü kod içerdiği anlamına
gelmez — aynı kapanış türü ya da aynı görev türü başka bir koşumda çağrılabilir ya da
yoklanabilirdi. Ölçülen, kaynağın taşıdığı olasılık değil, bu belirli koşumda hangi yolun
seçildiği.

#### Ölçüm Çekirdeği

Kursun geri kalanında kesitleri alınacak çekirdek `ilerle.rs`dir. On üç ayrı birimi tek
koşumda başlatır, sınıflandırır ve bir kanal üzerinden gelen kayıtları toplar. Kayıtlar bir
karma yapıya değil sıradan bir listeye yazılıyor ve okunmadan önce sıralanıyor — hangi iş
parçacığının hangi sırayla kaydını gönderdiği bu kursta hiçbir yerde ölçülmüyor ve
basılmıyor, yalnızca kaydın var olup olmadığı ölçülüyor.

```rust
// ilerle.rs — bir is ilerlemek icin neye ihtiyac duyuyor: kimseye mi, bir yoklayiciya mi,
// bir baskasinin bitmesine mi, yoksa hic ilerlemiyor mu.

use std::future::Future;
use std::pin::Pin;
use std::sync::mpsc::{channel, Sender};
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};
use std::thread;

// --- elle yazilan yurutucu: standart kitaplikta yurutucu yoktur ---

struct Uyandirici {
    hazir: Mutex<bool>,
    kosul: Condvar,
}

impl Wake for Uyandirici {
    fn wake(self: Arc<Self>) {
        *self.hazir.lock().unwrap() = true;
        self.kosul.notify_one();
    }
}

fn kostur<F: Future>(gorev: F) -> (F::Output, u32) {
    let mut gorev = Box::pin(gorev);
    let u = Arc::new(Uyandirici {
        hazir: Mutex::new(false),
        kosul: Condvar::new(),
    });
    let waker: Waker = u.clone().into();
    let mut baglam = Context::from_waker(&waker);
    let mut yoklama = 0;
    loop {
        yoklama += 1;
        match gorev.as_mut().poll(&mut baglam) {
            Poll::Ready(x) => return (x, yoklama),
            Poll::Pending => {
                let mut h = u.hazir.lock().unwrap();
                while !*h {
                    h = u.kosul.wait(h).unwrap();
                }
                *h = false;
            }
        }
    }
}

// --- bir kez duraklayip devam eden gorev ---

struct BirKezDuraklar {
    duraklandi: bool,
    ad: &'static str,
    kanal: Sender<String>,
}

impl Future for BirKezDuraklar {
    type Output = &'static str;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
        if self.duraklandi {
            self.kanal.send(self.ad.to_string()).unwrap();
            Poll::Ready(self.ad)
        } else {
            self.duraklandi = true;
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

struct HemenHazir {
    ad: &'static str,
    kanal: Sender<String>,
}

impl Future for HemenHazir {
    type Output = &'static str;
    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<&'static str> {
        self.kanal.send(self.ad.to_string()).unwrap();
        Poll::Ready(self.ad)
    }
}

async fn once_icini_bekle(ic: BirKezDuraklar, kanal: Sender<String>) -> &'static str {
    let _ = ic.await;
    kanal.send(String::from("gorev-await-eden")).unwrap();
    "await-eden"
}

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

    // --- kendi basina ilerleyenler: baslatildiktan sonra kimseye ihtiyaclari yok ---
    for i in 1..=3 {
        let g = gonder.clone();
        thread::spawn(move || {
            g.send(format!("iplik-kendi-basina-{}", i)).unwrap();
        });
    }

    // --- baskasinin bitmesini bekleyenler ---

    // (a) ana is parcacigi birlestirmeyi bekliyor
    let g = gonder.clone();
    let tutamac = thread::spawn(move || {
        g.send(String::from("iplik-birlestirilen")).unwrap();
    });
    tutamac.join().unwrap();
    gonder.send(String::from("ana-birlestirme-sonrasi")).unwrap();

    // (b) bir is parcacigi kanaldan haber bekliyor
    let (isaret_gonder, isaret_al) = channel::<()>();
    let g = gonder.clone();
    let bekleyen = thread::spawn(move || {
        isaret_al.recv().unwrap();
        g.send(String::from("iplik-kanal-bekleyen")).unwrap();
    });
    let g = gonder.clone();
    thread::spawn(move || {
        g.send(String::from("iplik-isaret-veren")).unwrap();
        isaret_gonder.send(()).unwrap();
    });
    bekleyen.join().unwrap();

    // --- hic baslatilmayan ---
    let g = gonder.clone();
    let _hic_baslatilmayan = move || {
        g.send(String::from("hic-baslatilmayan")).unwrap();
    };

    // --- yoklaninca ilerleyenler ---
    let (_, yoklama_a) = kostur(BirKezDuraklar {
        duraklandi: false,
        ad: "gorev-yoklanan-a",
        kanal: gonder.clone(),
    });
    let (_, yoklama_b) = kostur(HemenHazir {
        ad: "gorev-hazir-b",
        kanal: gonder.clone(),
    });

    // --- await eden: icteki bitmeden ilerlemiyor ---
    let ic = BirKezDuraklar {
        duraklandi: false,
        ad: "gorev-ic",
        kanal: gonder.clone(),
    };
    let (_, yoklama_c) = kostur(once_icini_bekle(ic, gonder.clone()));

    // --- hic yoklanmayan gorev ---
    let _hic_yoklanmayan = BirKezDuraklar {
        duraklandi: false,
        ad: "gorev-hic-yoklanmayan",
        kanal: gonder.clone(),
    };

    // --- rapor: beklenen sayida kayit okunur ve siralanir ---
    let beklenen = 3 + 2 + 2 + 2 + 2; // kendi basina 3, birlestirme 2, kanal 2, yoklanan 2, await 2
    let mut kayitlar: Vec<String> = Vec::new();
    for _ in 0..beklenen {
        kayitlar.push(al.recv().unwrap());
    }
    kayitlar.sort();

    println!("kaydedilen ilerlemeler ({}):", kayitlar.len());
    for k in &kayitlar {
        println!("  {}", k);
    }

    println!();
    println!("yoklama sayilari: a={} b={} await={}", yoklama_a, yoklama_b, yoklama_c);

    let siniflar: [(&str, &str); 13] = [
        ("iplik-kendi-basina-1", "kendi basina"),
        ("iplik-kendi-basina-2", "kendi basina"),
        ("iplik-kendi-basina-3", "kendi basina"),
        ("iplik-isaret-veren", "kendi basina"),
        ("iplik-birlestirilen", "kendi basina"),
        ("ana-birlestirme-sonrasi", "baskasi bitince"),
        ("iplik-kanal-bekleyen", "baskasi bitince"),
        ("gorev-await-eden", "baskasi bitince"),
        ("gorev-yoklanan-a", "yoklaninca"),
        ("gorev-hazir-b", "yoklaninca"),
        ("gorev-ic", "yoklaninca"),
        ("hic-baslatilmayan", "hic"),
        ("gorev-hic-yoklanmayan", "hic"),
    ];

    println!();
    println!("{:<26}{:<18}{}", "birim", "sinif", "kayit var mi");
    for (ad, sinif) in siniflar {
        let var = kayitlar.iter().any(|k| k == ad);
        println!("{:<26}{:<18}{}", ad, sinif, if var { "evet" } else { "hayir" });
    }

    println!();
    for s in ["kendi basina", "yoklaninca", "baskasi bitince", "hic"] {
        let adet = siniflar.iter().filter(|(_, k)| *k == s).count();
        println!("{:<18}{}", s, adet);
    }
    println!("toplam birim: {}", siniflar.len());
}
```

```
kaydedilen ilerlemeler (11):
  ana-birlestirme-sonrasi
  gorev-await-eden
  gorev-hazir-b
  gorev-ic
  gorev-yoklanan-a
  iplik-birlestirilen
  iplik-isaret-veren
  iplik-kanal-bekleyen
  iplik-kendi-basina-1
  iplik-kendi-basina-2
  iplik-kendi-basina-3

yoklama sayilari: a=2 b=1 await=2

birim                     sinif             kayit var mi
iplik-kendi-basina-1      kendi basina      evet
iplik-kendi-basina-2      kendi basina      evet
iplik-kendi-basina-3      kendi basina      evet
iplik-isaret-veren        kendi basina      evet
iplik-birlestirilen       kendi basina      evet
ana-birlestirme-sonrasi   baskasi bitince   evet
iplik-kanal-bekleyen      baskasi bitince   evet
gorev-await-eden          baskasi bitince   evet
gorev-yoklanan-a          yoklaninca        evet
gorev-hazir-b             yoklaninca        evet
gorev-ic                  yoklaninca        evet
hic-baslatilmayan         hic               hayir
gorev-hic-yoklanmayan     hic               hayir

kendi basina      5
yoklaninca        3
baskasi bitince   3
hic               2
toplam birim: 13
```

**IP1.** Bu koşum belirlenimcidir: beş ayrı koşumda aynı on üç birim, aynı sıralı listede ve
aynı sınıf dağılımıyla üretildiği doğrulandı. **On üç birimin 5'i kendi başına, 3'ü
yoklanınca, 3'ü başkası bitince ilerledi; 2'si hiç ilerlemedi.** Bu sayı kursun geri kalanında
değişmiyor; her ders yalnız kendi kesitini alıyor.

Beş `kendi başına` satırının üçü döngüyle başlatılan bağımsız iş parçacıkları, biri sinyal
gönderen iş parçacığı, biri de birleştirilmeyi bekleyen tarafın başlattığı iş parçacığının
kendisi. Bu beşinin ortak noktası, başlatıldıkları andan kayıtlarını gönderdikleri ana kadar
kimseden bir şey beklememeleri. Üç `başkası bitince` satırından ikisi iş parçacığı düzeyinde:
ana iş parçacığı birleştirme çağrısı bitene kadar, sinyal bekleyen iş parçacığı da kanaldan
bir haber gelene kadar ilerleyemiyor. Üçüncüsü, `gorev-await-eden`, bu dersin konusu değil —
o bir eşzamansız görevin dış katmanı ve Eşzamansız Programlama konusunda ele alınacak. Üç
`yoklanınca` satırı da aynı konuya ait: bu ders yalnız onların da kaynakta bulunduğunu ve
rapor tablosunda kayıtlarının göründüğünü not ediyor, yoklama düzeneğinin kendisini
açıklamıyor.

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

Kayıtların **sırası** bu derste hiçbir yerde ölçülmüyor: üç bağımsız iş parçacığı hangi
sırayla kaydını gönderirse göndersin, rapor onları önce topluyor, sonra sıralıyor, ancak
ondan sonra yazdırıyor. Bunun nedeni bir eksiklik değil, bilinçli bir sınır: zamanlayıcının
üç iş parçacığını hangi sırayla çalıştıracağı ortama bağlıdır ve bu kurs ortama bağlı hiçbir
değeri basmıyor. Ölçülen şey, beklenen sayıda kaydın **var olup olmadığı** — kaç tane
geldiği ve hangi isimlerin geldiği — sırası değil.

Bu sınır, çekirdeğin `beklenen` sabitinde de görünür: rapor tam olarak on bir kayıt bekliyor
ve o kadarını okuyor, ne bir fazlasını ne bir eksiğini. Eğer bir iş parçacığı kaydını hiç
göndermeseydi, program o kaydı sonsuza kadar beklerdi — bu yüzden `hiç` sınıfına giren iki
birim (`hic-baslatilmayan` kapanışı ve `gorev-hic-yoklanmayan` görevi) rapor kanalına hiç
dokunmuyor; onların kaydı olmayışı `beklenen` sayısının dışında tutularak, yani program
onları hiç beklemeyerek ölçülüyor.

Bu tasarım kararının kendisi bir öğreti taşıyor: bir programın kaç birim beklemesi gerektiği,
koşum sırasında keşfedilen bir şey değil, kaynakta önceden bilinen bir sayıdır. `beklenen`
üç iş parçacığın kendi başlattığı üç kaydını, birleştirme çiftinin iki kaydını, kanal
çiftinin iki kaydını, iki yoklanan görevin iki kaydını ve bekleyen görevin iki kaydını topluyor
— toplamda on bir. Bu toplam elle hesaplanmış bir sabittir, koşumdan öğrenilmiş bir değer
değil; kurs boyunca kullanılacak yöntem budur: beklenen sayı önce kaynaktan çıkarılır, sonra
koşumun o sayıyı karşılayıp karşılamadığına bakılır.

#### Özet

- Kursun ölçü ekseni bir birimin ilerlemek için neye ihtiyaç duyduğudur; dört sınıf kurs
  boyunca değişmez: kendi başına, yoklanınca, başkası bitince, hiç.
- Ölçüm çekirdeği on üç birimi sınıflandırıyor: 5'i kendi başına, 3'ü yoklanınca, 3'ü başkası
  bitince ilerledi, 2'si hiç ilerlemedi; bu sayı kurs boyunca değişmiyor.
- Başlatılan bir iş parçacığı, çalışmaya başladığı andan itibaren kendi başına ilerler; onu
  birleştirmek için bekleyen taraf başkası bitince ilerler.
- Kayıtların gönderilme sırası ölçülmez ve basılmaz; ölçülen, beklenen sayıda kaydın var
  olup olmadığıdır.
- JVM, Eşzamanlılık ve Başarım kursu bir adımı hangi iş parçacığının koşturduğunu ölçtü, bu
  kurs aynı adımın ilerlemek için neye ihtiyaç duyduğunu ölçüyor.

#### Sonraki Adım

Sinyal bekleyen iş parçacığı bu derste bir işaret kanalıyla ilerledi; sıradaki ders aynı
düzeneği bir yükle çalıştırıyor. Bir kanaldan sahipliğiyle birlikte gelen bir değer, onu
gönderen tarafı kendi başına bırakırken alan tarafı başkası bitince bekletiyor — sıradaki
ders bu ikiliyi bir mesajlaşma örneğiyle ölçüyor.

### Kanallarla Mesajlaşma

Source: https://academia.sh/tr/kurslar/rust-eszamanlilik/kanallarla-mesajlasma

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.

### İş Parçacıkları Arası Güvenliğin Tipteki Karşılığı

Source: https://academia.sh/tr/kurslar/rust-eszamanlilik/is-parcaciklari-arasi-guvenligin-tipteki-karsiligi

Bir değerin bir iş parçacığı sınırını geçip geçemeyeceği tipte yazılı bir koşuldur: geçebilen bir tasarım gerçekten geçiriliyor ve kaydı okunuyor, ama tipin izin vermesi paylaşılan bir hesabın bağımsız varsayımla aynı sonucu vereceğini göstermiyor.

Önceki ders bir değerin bir iş parçacığından diğerine geçebildiğini iki örnekle gösterdi:
bir liste, bir ileti yapısı. İkisi de sorunsuzca geçti, ama bu geçişin kendisi hiç
sorgulanmadı — sanki her tip bir kanaldan ya da bir paylaşılan kaba geçebilirmiş gibi
davranıldı. Aslında geçemeyen tipler var ve bu izin çalışma zamanında değil **derleme
zamanında** kontrol ediliyor. Bu dersin sorusu şu: bir tipin sınırı geçebilmesi neye bağlı
ve bu koşul kaynakta nerede yazılı.

JVM, Eşzamanlılık ve Başarım kursu veri yarışı kavramını kurdu: iki iş parçacığının aynı
belleğe eşzamanlı ve en az biri yazarak eriştiği, sonucun tanımsız olduğu durum. Bu kavram
burada yeniden tanımlanmıyor. Rust'ın farkı, bu riski çalışma zamanında bir saptayıcıyla
yakalamak yerine, riski taşıyan tipleri iş parçacığı sınırını geçmekten **baştan** alıkoyması.
Bir değerin veri yarışına açık olup olmadığı, o değer paylaşılmadan önce, kaynak derlenirken
biliniyor.

#### `Send` ve `Sync`: Tipte Yazılı Bir İzin

Sahiplik ve Bellek Modeli kursu sahiplik, ödünç alma ve referans kavramlarını kurdu, ama o
kurs bu kavramları tek bir iş parçacığı varsayımıyla anlattı. Bir değerin başka bir iş
parçacığına **taşınabilmesi** ile bir değere başka bir iş parçacığından **referans
verilebilmesi**, ayrı iki koşuldur ve Rust bu ikisini iki ayrı tipe bağlıyor: `Send` ve
`Sync`. Bir tip `Send` ise, o tipten bir değerin sahipliği başka bir iş parçacığına
güvenle taşınabilir. Bir tip `Sync` ise, o tipe bir referans (`&T`) başka bir iş
parçacığından güvenle okunabilir — yani `T`, `Sync` ise `&T` de `Send`dir.

Bu ikisi çevrilmeyen tip adlarıdır ve çoğu tip için derleyici tarafından **otomatik**
kurulur: bir yapının bütün alanları `Send` ise yapının kendisi de `Send`dir, aynı kural
`Sync` için de geçerli. Programcı bu iki tipi genellikle hiç yazmaz; yalnızca bir değeri bir
iş parçacığı sınırının üzerinden geçirmeye çalıştığında, derleyici o değerin tipinin bu
izne sahip olup olmadığını kontrol eder. İzin yoksa kaynak derlenmez — ama bu kısıt bu
derste bir hata olarak koşturulmuyor, yalnızca düzyazıda tarif ediliyor.

Bu otomatik kurulma, önceki iki dersin örneklerinin neden hiç sorgulanmadan geçtiğini de
açıklıyor. Önceki dersteki `Vec<i32>` ve `Ileti` yapısı — bir dizge ve bir tam sayı alanı
taşıyan basit bir yapı — hem `Send` hem `Sync`. Bunun nedeni, ikisinin de yalnızca temel
tiplerden ve standart kitaplığın kendisi `Send`/`Sync` işaretli koleksiyonlarından kurulmuş
olması. Bir tipin bu izinlerden birini **kaybetmesi** için, içinde özel bir paylaşım
düzeneği barındırması gerekiyor — tam da bir sonraki bölümde görülecek `Rc` ve `RefCell`
gibi. Derleyici bu izni tip tanımına bakarak çıkarıyor; programcı `Send` ya da `Sync`
sözcüğünü kaynakta hiç yazmasa da, her `struct` ve `enum` bildirimi örtük olarak bu iki
sınıflandırmadan birine giriyor.

#### Geçebilen Tasarım

Sahiplik ve Bellek Modeli kursunun Atomik Referans Sayımı dersi `Arc`ı tanıttı, ama o
derste ölçülen yalnızca sayacın atomik olup olmamasıydı — sınır geçme sorusu orada açıkça
dışarıda bırakılmıştı. Bu ders o sınırı ölçüyor: `Arc`ın atomik sayacı, onu hem `Send` hem
`Sync` yapan koşulun ta kendisi. İçindeki tip de bu iki izne sahipse (`Mutex<T>` her zaman
`Send`dir ve içindeki `T`, `Send` ise `Mutex<T>` de `Sync`dir), `Arc<Mutex<T>>` bütünüyle
iş parçacığı sınırını geçebilen bir tip oluyor.

Aşağıdaki program, alanlarının hepsi `Send` olan bir yapıyı `Arc<Mutex<...>>` içine
yerleştirip iki iş parçacığı arasında paylaşıyor. `Arc` hem `Send` hem `Sync`dir (içindeki
tip de öyleyse), `Mutex` de aynı şekilde — bu yüzden `kayit` değeri her iki iş parçacığına
da klonlanan bir tutamaçla güvenle geçebiliyor.

```rust
// sinir.rs — bir degerin is parcacigi sinirini gecebilmesi tipte yazili bir kosuldur.

use std::sync::{Arc, Mutex};
use std::thread;

struct PaylasilanKayit {
    etiket: String,
    deger: i32,
}

fn main() {
    let kayit = Arc::new(Mutex::new(PaylasilanKayit {
        etiket: String::from("olusum"),
        deger: 5,
    }));

    let k1 = Arc::clone(&kayit);
    let birinci = thread::spawn(move || {
        let mut icerik = k1.lock().unwrap();
        icerik.deger += 1;
        icerik.etiket = String::from("birinci-yazdi");
    });
    birinci.join().unwrap();

    let k2 = Arc::clone(&kayit);
    let ikinci = thread::spawn(move || {
        let icerik = k2.lock().unwrap();
        (icerik.deger, icerik.etiket.clone())
    });
    let (okunan_deger, okunan_etiket) = ikinci.join().unwrap();

    println!("sinir gecti — deger: {}, etiket: {}", okunan_deger, okunan_etiket);
}
```

```
sinir gecti — deger: 6, etiket: birinci-yazdi
```

**IP4.** `birinci` iş parçacığı kilit boşken çağrıldığı için kimseye ihtiyaç duymadan
ilerliyor — kendi başına sınıfına giriyor. `ikinci` iş parçacığı da aynı şekilde başlıyor,
ama onun asıl bağımlılığı `birinci.join()` çağrısının ana akışta önce tamamlanmış olması:
program ikinci klonu göndermeden önce ilk iş parçacığını birleştiriyor, böylece okunan
değerin `birinci`nin yazdığı değer olduğu güvenceye bağlanıyor. Kayıt tipinin sınırı
geçebilmesi, bu iki iş parçacığının aynı veriye sırayla erişebilmesinin ön koşulu; koşulun
kendisi çalışma zamanında değil, `Arc<Mutex<PaylasilanKayit>>` yazıldığı anda, derleme
zamanında sağlanıyor.

`Arc::clone` çağrısının kendisi de dikkat çekici: `k1` ve `k2`, aynı `Mutex`e işaret eden
iki ayrı tutamaç, ama ikisi de aynı öbek nesnesini paylaşıyor. Bu, önceki dersteki kanal
tutamacının klonlanmasıyla aynı düzenek — `Arc::clone` da veriyi kopyalamıyor, yalnızca
paylaşılan sahiplik sayacını bir artırıyor. Aradaki fark, kanalın klonlanan ucunun bir
gönderim kapısı olması, `Arc`ın klonlanan ucunun ise doğrudan paylaşılan veriye erişim
noktası olması. `kayit.lock()` çağrısı bu noktada devreye giriyor: `Arc` paylaşılan
erişimi mümkün kılıyor, `Mutex` ise o erişimi aynı anda yalnızca bir tarafa veriyor. İkisi
ayrı sorunları çözüyor — biri "kaç tarafın bu veriye referansı var" sorusunu, öbürü "şu anda
kim yazabilir" sorusunu.

#### Geçemeyen Tip: Neden `Rc<RefCell<..>>` Sınırı Aşamaz

Sahiplik ve Bellek Modeli kursunun paylaşımlı sahiplik payı `Rc` ve `RefCell` çiftini tek iş
parçacıklı bir bağlamda tanıttı: `Rc`, bir değerin birden fazla sahibi olmasını sayarak
izliyor, `RefCell` ise ödünç kurallarını derleme yerine çalışma zamanında denetliyor. Bu
ikisi birlikte kullanıldığında `Rc<RefCell<T>>`, aynı `sinir.rs`deki `Arc<Mutex<...>>` gibi
paylaşılan ve değiştirilebilir bir kap gibi görünüyor — ama bu kap bir iş parçacığı
sınırını geçemez.

Sebep sayaçların kendisinde. `Rc`nin tuttuğu referans sayısı, iki iş parçacığı aynı anda
artırıp azaltabilseydi bir veri yarışına açık olurdu, çünkü bu sayaç atomik değildir; aynı
şekilde `RefCell`in çalışma zamanı ödünç sayacı da atomik değildir. Derleyici bu iki tipin
ikisini de `Send` **olarak işaretlemez** — bu, bir eksiklik değil, tam olarak veri
yarışının önlenmesinin kaynağıdır. Bir programcı `Rc<RefCell<PaylasilanKayit>>` değerini
`thread::spawn`e vermeye çalışsaydı, kaynak derlenmezdi; bu redd burada bir hata metniyle
gösterilmiyor, yalnızca nedeni anlatılıyor. Kabul edilen karşılığı zaten yukarıda koşturuldu:
aynı ihtiyaç, sayaçları atomik olan `Arc` ve kilitleme ile koruma sağlayan `Mutex` ile
karşılanıyor. İkisi arasındaki fark yalnız isim değil — `Arc`nin sayacı atomik işlemlerle
güncellenir, `Mutex` da erişimi sırayla verir; ikisi birlikte, `Rc<RefCell<..>>`in tek iş
parçacıklı dünyada üstlendiği rolü, birden fazla iş parçacığının bulunduğu bir dünyada
üstleniyor.

Bu ayrım, tek başına `Mutex` kullanmadan da fark ediliyor. Bir tipin yalnızca `RefCell`
taşıması (paylaşımlı sahiplik olmadan) onu `Sync` olmaktan çıkarır ama `Send` olmaktan
çıkarmaz — bir `RefCell<T>` başka bir iş parçacığına tek başına taşınabilir, çünkü o iş
parçacığı taşındıktan sonra tek sahip olur; ama iki iş parçacığı arasında **paylaşılan bir
referansla** kullanılamaz, çünkü çalışma zamanı ödünç sayacı korumasız kalır. `Rc<T>` ise
ikisini de kaybeder, çünkü `Rc`nin var oluş nedeni zaten paylaşılan sahipliktir ve o
paylaşım iş parçacıkları arasında hiçbir zaman güvenli değildir. Bu üç örnek — sade
`RefCell`, sade `Rc` ve ikisinin birleşimi — aynı kökten gelen üç farklı redde işaret
ediyor: iç değişebilirlik, referans sayımı, ya da ikisinin toplamı; hangisi olursa olsun
kaynaktaki koşul aynı, atomik olmayan bir sayaç ya da atomik olmayan bir denetim
mekanizması.

Bu redd derleme hatasıyla gösterilmiyor olsa da, sonucu Trait'ler, Genellikler ve Hata
Yönetimi kursunda kurulan bir kalıba benziyor: orada da bir tipin belirli bir sınırı
sağlamadığı durumlar kod bloğuna konmadan, yalnızca gerekçesiyle anlatılmıştı. Buradaki
fark, orada ölçülen sınırların genellik (bir tip parametresinin hangi trait'leri
sağlaması gerektiği) üzerine olması, burada ölçülenin ise iş parçacığı sınırını geçme
izni olması — ikisi de derleyicinin bir tipin ne yapabileceğine dair kararını, kodu hiç
çalıştırmadan verdiği örnekler.

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

Tipin bir paylaşıma izin vermesi, o paylaşımı kullanan programın **doğru** olduğu anlamına
gelmiyor. `Arc<Mutex<i32>>` veri yarışını engeller — aynı anda iki iş parçacığı aynı tam
sayıyı değiştiremez — ama hangi işlemin hangi sırayla uygulandığı hâlâ programın kendi
tasarımına bağlıdır. Aşağıdaki ölçüm bunu doğrudan gösteriyor: iki iş parçacığının her biri
paylaşılan bir sayıyı bağımsızca değiştirdiğini varsayarsa beklenen toplam bir değerdir; ama
aynı sayının üzerinde sırayla çalıştıklarında sonuç başka bir değer olur.

Bu, kilitli bir kaynağı paylaşan iki iş parçacığının karşılaşabileceği sorunlardan yalnızca
biri; bir başkası, iki iş parçacığının birbirinin tuttuğu kilidi beklemesi durumunda ortaya
çıkabilir ve bu kurs böyle bir durumu hiç koşturmuyor, çünkü sonucu bir kilitlenmedir ve
belirlenimci bir çıktı üretmez. Burada ölçülen daha sınırlı ve daha temel bir soru: kilidin
kendisi doğru çalışsa bile — hiçbir kilitlenme olmasa, hiçbir veri yarışı olmasa bile —
paylaşılan bir değer üzerinde çalışan iki işlemin sırası, o işlemlerin sonucunu değiştirir
mi. Yanıt evet ve bu, tipin verdiği güvencenin sınırını çiziyor.

```rust
// sinirlayici.rs — tip paylasima izin verir ama sira bagimliligi yanlis sonuc verebilir.

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let baslangic = 5;

    // bagimsiz varsayimla beklenen: A degeri ikiye katlar (10), B kendi payinda 10 ekler (15),
    // ikisi ayri calissaydi toplam 10 + 15 = 25 olurdu.
    let bagimsiz_beklenen = (baslangic * 2) + (baslangic + 10);

    let paylasilan = Arc::new(Mutex::new(baslangic));

    let p1 = Arc::clone(&paylasilan);
    let a = thread::spawn(move || {
        let mut deger = p1.lock().unwrap();
        *deger *= 2;
    });
    a.join().unwrap();

    let p2 = Arc::clone(&paylasilan);
    let b = thread::spawn(move || {
        let mut deger = p2.lock().unwrap();
        *deger += 10;
    });
    b.join().unwrap();

    let gercek_sonuc = *paylasilan.lock().unwrap();

    println!("bagimsiz varsayimla beklenen: {}", bagimsiz_beklenen);
    println!("paylasilan mutex ile gercek sonuc: {}", gercek_sonuc);
    println!(
        "tip paylasima izin verdi, sonuc bagimsiz varsayimla ayni mi: {}",
        gercek_sonuc == bagimsiz_beklenen
    );
}
```

```
bagimsiz varsayimla beklenen: 25
paylasilan mutex ile gercek sonuc: 20
tip paylasima izin verdi, sonuc bagimsiz varsayimla ayni mi: false
```

**IP5.** Beklenen değer 25, iki işlemin birbirinden habersiz, kendi başlangıç değeri
üzerinde çalıştığı varsayımıyla hesaplanıyor: `A` beşi ikiye katlayıp on buluyor, `B` beşe on
ekleyip on beş buluyor, toplam yirmi beş. Gerçek koşumda ise ikisi **aynı** sayıyı
paylaşıyor ve program `A`yı `B`den önce birleştiriyor: `A` beşi ikiye katlayıp ona
çıkarıyor, `B` bu ona on daha ekleyip yirmiye çıkarıyor. Sonuç yirmi, yirmi beş değil — ve bu
fark üç ayrı koşumda birebir aynı çıkıyor, çünkü birleştirme çağrıları sırayı belirlenimci
kılıyor. `Mutex<i32>` tipi bu paylaşıma izin verdi ve veri yarışını da gerçekten engelledi;
engellemediği şey, programcının "bu iki işlem birbirinden bağımsızdır" varsayımının doğru
olup olmadığı. Hangi işlemin **önce** kilit alacağı hâlâ programın tasarımına bağlı bir
sorudur ve rastgele hangisinin kazanacağı bu derste hiç ölçülmüyor — yalnızca sabit bir
sıraya (önce `A`, sonra `B`) zorlanan bu belirli tasarımın sonucu ölçülüyor.

Bu ölçümün asıl öğrettiği, "tip güvenlidir" ile "program doğrudur" iddialarının farklı
şeyler olduğu. `Mutex<i32>` etrafındaki tip sistemi, iki iş parçacığının aynı tam sayıyı
aynı anda değiştirmesini engeller — bu güvence her koşumda geçerlidir ve hiç bozulmaz. Ama
kilidin **hangi sırayla** verileceği, hangi işlemin diğerinden önce çalışacağı, tamamen
programın kendi tasarımına bağlıdır ve tip sistemi bu konuda hiçbir şey söylemez. Muteks ve
Okuma/Yazma Kilidi dersi bu boşluğu doğrudan konu edinecek: bir tarafın kilidi ne zaman
bekleyeceği ve bu beklemenin ilerleme sınıflarından hangisine düştüğü.

#### Özet

- `Send` bir tipin sahipliğinin başka bir iş parçacığına taşınabildiğini, `Sync` bir tipe
  referansın başka bir iş parçacığından okunabildiğini söyler; ikisi de derleme zamanında
  denetlenir.
- Alanlarının hepsi `Send`/`Sync` olan bir yapı, `Arc<Mutex<...>>` içine konduğunda sınırı
  geçebiliyor; ölçümde `birinci` kendi başına ilerledi, `ikinci` `birinci`nin birleştirmesi
  tamamlanana kadar bekledi.
- `Rc<RefCell<..>>` sınırı geçemez, çünkü ikisinin de sayacı atomik değildir; kabul edilen
  karşılığı `Arc` (atomik sayaç) ve `Mutex` (sıralı erişim) birlikte sağlıyor.
- Tipin bir paylaşıma izin vermesi veri yarışını engeller, ama programın doğru sonucu
  vereceğini garanti etmez — sıraya bağımlı bir hesap, bağımsız varsayımdan farklı bir
  sonuç üretebilir.
- Hangi işlemin kilidi önce alacağı bu derste ölçülmüyor; yalnızca sabit bir sıraya zorlanan
  belirli bir tasarımın belirlenimci sonucu ölçülüyor.

#### Sonraki Adım

Bu derste iki iş parçacığı aynı veriye sırayla erişti, çünkü program birleştirme çağrılarıyla
sırayı kendisi zorladı: `birinci` tamamen bitmeden `ikinci` hiç başlamadı. Ama birleştirme
her zaman uygun bir araç değil — bazen iki iş parçacığının aynı anda çalışıyor olması
gerekiyor ve yalnızca paylaşılan veriye erişim anında sıraya girmesi isteniyor. Bu durumda
bir iş parçacığının diğerinin kilidi bırakmasını beklemesi gerekiyor ve bu bekleme
birleştirmeden farklı bir düzenek. Sıradaki ders bu beklemeyi doğrudan ölçüyor: kilidi
tutan kendi başına ilerlerken, kilidi bekleyen başkası bitince nasıl ilerliyor — ve okuma
amaçlı bir kilidin, yazma amaçlı bir kilitten neden daha gevşek bir kural taşıdığı.

### Muteks ve Okuma/Yazma Kilidi

Source: https://academia.sh/tr/kurslar/rust-eszamanlilik/muteks-ve-okuma-yazma-kilidi

Bir dışlama kilidini tutan kendi başına ilerler, onu bekleyen başkası bitince ilerler; bu sıra bir kanal el sıkışmasıyla belirlenimci kılınır ve okuma/yazma kilidinin okuma tarafı, aynı anda birden fazla okuyucuya izin vererek dışlama kilidinden ayrılır.

Önceki ders paylaşılan bir tam sayı üzerinde iki iş parçacığının sırayla çalıştığını, ama bu
sırayı birleştirme çağrılarıyla kendisinin zorladığını gösterdi: `birinci` tamamen bitmeden
`ikinci` hiç başlamadı. Bu iki işlemin aynı anda çalışıp yalnızca paylaşılan veriye erişim
anında sıraya girmesi gerektiğinde, birleştirme yeterli bir araç değil — iki iş parçacığının
geri kalanı paralel ilerlerken yalnızca kritik bölgeye giriş anlarının sıraya girmesi
gerekiyor. Bu ders bekleyen bir tarafın kilit üzerinden nasıl ilerlediğini doğrudan ölçüyor.

Bu derste iki farklı kilit türü koşturuluyor: `Mutex`, tek bir tarafın aynı anda erişmesine
izin veren bir dışlama kilidi; `RwLock`, okuma ve yazma erişimini ayrı ayrı ele alan bir
okuma/yazma kilidi. İkisi de aynı temel soruna çözüm sunuyor — paylaşılan bir veriye
eşzamanlı erişimi güvenli kılmak — ama izin verdikleri paylaşımın derecesi farklı. Ders bu
farkı üç ayrı senaryoyla ölçüyor: kilidi tutan tarafın kendi başına ilerlemesi, kilidi
bekleyen tarafın başkası bitince ilerlemesi ve okuma/yazma kilidinin okuma tarafının bu
kuralı nasıl gevşettiği.

#### Kilidi Tutan Kendi Başına, Bekleyen Başkası Bitince

Bir dışlama kilidi kavramı burada yeniden kurulmuyor. İşletim Sistemi Kavramları kursu
kilidin ne için var olduğunu, JVM, Eşzamanlılık ve Başarım kursu bir kilidi tutan iş
parçacığının kimliğini ölçtü. Bu kurs farklı bir soru soruyor: kilidi **tutan** tarafın ve
kilidi **bekleyen** tarafın ilerlemek için neye ihtiyaç duyduğu. Yanıt kısa: kilit boşken
çağrılan bir `lock` kimseye ihtiyaç duymadan döner — kendi başına sınıfına girer. Kilit
doluyken çağrılan bir `lock` ise, kilidi tutan taraf onu bırakana kadar bekler — başkası
bitince sınıfına girer. Bu, Kanallarla Mesajlaşma dersindeki `recv` çağrısıyla aynı yapıya
sahip: ikisi de kendi dışında bir olayın gerçekleşmesini bekliyor, farkları beklenen olayın
bir gönderim mi yoksa bir serbest bırakma mı olması.

Kilidin kendisi, `lock` çağrısının döndürdüğü bir tutamaçla temsil ediliyor. Bu tutamaç
kapsam dışına çıktığında (ya da açıkça bırakıldığında) kilit otomatik olarak serbest kalıyor
— Sahiplik ve Bellek Modeli kursunun kurduğu bırakma kuralının bir uzantısı. Aşağıdaki
programda `tutan` iş parçacığının içindeki `kayit` adlı tutamaç, işlevin gövdesi bitip
kapsam dışına çıkana kadar kilidi elinde tutuyor; kilidin ne zaman bırakılacağı programcının
açık bir çağrısına değil, bu tutamacın ömrünün nerede bittiğine bağlı. Bu, önceki derste
görülen taşınan bir değerin sahiplik kuralıyla aynı düzenek — burada uygulanan yer kilit
tutamacı.

Bu ilişkiyi çalışma zamanının gerçek zamanlamasına bakmadan ölçmek gerekiyor, çünkü
zamanlama ortama bağlıdır ve bu kursta basılmıyor. Aşağıdaki program, kilidi tutan tarafın
kilide gerçekten dokunduğunu ve bekleyen tarafın gerçekten ondan sonra girdiğini bir kanal
el sıkışmasıyla kanıtlıyor: bekleyen taraf, tutan tarafın kilide girdiğine dair bir sinyal
almadan denemeye bile başlamıyor ve iki tarafın da paylaşılan bir deftere yazdığı kayıtların
sırası, kilidin kendisi tarafından zorlanıyor. Bu el sıkışma olmasaydı — yani `bekleyen`
kendi kilit çağrısını herhangi bir sinyal beklemeden yapsaydı — ölçüm hâlâ doğru sırayı
üretirdi, çünkü kilit zaten mutlak bir sıra garantisi veriyor; ama o durumda `bekleyen`in
çağrısının gerçekten `A` kilidi tutuyorken mi yoksa ondan önce mi yapıldığı belirsiz
kalırdı. El sıkışma, ölçümün **hangi anı** kanıtladığını netleştiriyor.

#### El Sıkışmayla Belirlenimci Sıra

Aşağıdaki program üç ayrı senaryoyu peş peşe koşturuyor ve üçü de aynı `main` işlevi
içinde, kendi kanallarıyla izole ediliyor. İlk senaryo tek bir bekleyeni ölçüyor ve iki ayrı
sinyal kanalı kullanıyor: `a_icerde_gonder`/`a_icerde_al`, `tutan`ın kilide girdiğini
`bekleyen`e bildiriyor; `devam_gonder`/`devam_al` ise `bekleyen`in denemeye başladığını
`tutan`a bildiriyor. Bu iki yönlü el sıkışma, hangi tarafın kilide ne zaman dokunduğunu
kesinleştiriyor — kilidin kendisi (`gunluk`) bu sinyalleşmeden habersiz, yalnızca standart
`lock` çağrısıyla korunuyor.

```rust
// kilit.rs — kilidi tutan kendi basina ilerler, kilidi bekleyen baskasi bitince ilerler.

use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex, RwLock};
use std::thread;

fn main() {
    // --- disari cikma sirasi el sikismayla belirlenimci kilinir ---
    let gunluk = Arc::new(Mutex::new(Vec::<String>::new()));
    let (a_icerde_gonder, a_icerde_al) = channel::<()>();
    let (devam_gonder, devam_al) = channel::<()>();

    let g = Arc::clone(&gunluk);
    let tutan = thread::spawn(move || {
        // kendi basina: kilit bostu, kimseyi beklemeden aliniyor
        let mut kayit = g.lock().unwrap();
        kayit.push(String::from("A-girdi"));
        a_icerde_gonder.send(()).unwrap();
        devam_al.recv().unwrap(); // B'nin denemeye basladigindan emin olunana kadar tutuluyor
        kayit.push(String::from("A-cikti"));
        // kayit burada kapsam disina cikip birakiliyor
    });

    a_icerde_al.recv().unwrap();
    let g = Arc::clone(&gunluk);
    let bekleyen = thread::spawn(move || {
        devam_gonder.send(()).unwrap();
        // baskasi bitince: A kilidi birakana kadar burada bloklanir
        let mut kayit = g.lock().unwrap();
        kayit.push(String::from("B-girdi"));
        kayit.push(String::from("B-cikti"));
    });

    tutan.join().unwrap();
    bekleyen.join().unwrap();

    println!("tek bekleyen sirasi: {:?}", *gunluk.lock().unwrap());

    // --- iki bekleyen: kimin once girecegi olculmez, yalniz ikisinin de girdigi olculur ---
    let gunluk2 = Arc::new(Mutex::new(Vec::<String>::new()));
    {
        let mut ilk = gunluk2.lock().unwrap();
        ilk.push(String::from("baslangic"));
    }

    let g = Arc::clone(&gunluk2);
    let c = thread::spawn(move || {
        let mut kayit = g.lock().unwrap();
        kayit.push(String::from("C-girdi"));
    });
    let g = Arc::clone(&gunluk2);
    let d = thread::spawn(move || {
        let mut kayit = g.lock().unwrap();
        kayit.push(String::from("D-girdi"));
    });
    c.join().unwrap();
    d.join().unwrap();

    let mut girenler: Vec<String> = gunluk2
        .lock()
        .unwrap()
        .iter()
        .filter(|s| s.ends_with("-girdi"))
        .cloned()
        .collect();
    girenler.sort();
    println!("iki bekleyenin girdigi kayitlar (sira olculmedi): {:?}", girenler);

    // --- okuma/yazma kilidi: birden fazla okuyucu ayni anda kilidi tutabiliyor ---
    let deger = Arc::new(RwLock::new(42));
    let (r1_icerde_gonder, r1_icerde_al) = channel::<()>();
    let (r2_tamam_gonder, r2_tamam_al) = channel::<()>();
    let (birak_gonder, birak_al) = channel::<()>();

    let d1 = Arc::clone(&deger);
    let okuyucu1 = thread::spawn(move || {
        let tutamac = d1.read().unwrap();
        r1_icerde_gonder.send(()).unwrap();
        birak_al.recv().unwrap();
        *tutamac
    });

    r1_icerde_al.recv().unwrap();
    let d2 = Arc::clone(&deger);
    let okuyucu2 = thread::spawn(move || {
        // R1 hala okuma kilidini tutuyorken bu cagri da basariyla kilit aliyor
        let tutamac = d2.read().unwrap();
        let sonuc = *tutamac;
        r2_tamam_gonder.send(()).unwrap();
        sonuc
    });

    r2_tamam_al.recv().unwrap();
    birak_gonder.send(()).unwrap();

    let v1 = okuyucu1.join().unwrap();
    let v2 = okuyucu2.join().unwrap();

    println!();
    println!("iki okuyucu ayni anda kilit tuttu, okunan degerler: {} ve {}", v1, v2);
    println!("cok okuyana izin verildi: {}", v1 == v2);
}
```

```
tek bekleyen sirasi: ["A-girdi", "A-cikti", "B-girdi", "B-cikti"]
iki bekleyenin girdigi kayitlar (sira olculmedi): ["C-girdi", "D-girdi"]

iki okuyucu ayni anda kilit tuttu, okunan degerler: 42 ve 42
cok okuyana izin verildi: true
```

**IP6.** İlk satırdaki dörtlü sıra üç ayrı koşumda birebir aynı çıkıyor:
`A-girdi, A-cikti, B-girdi, B-cikti`. Bu sıra zamanlamadan değil, programın yapısından
geliyor. `bekleyen` iş parçacığı, `tutan` kilide girdiğine dair sinyali (`a_icerde_al.recv()`)
almadan kendi kilit çağrısını hiç yapmıyor; `tutan` ise kendi kaydını (`A-girdi`) yazıp
sinyali gönderdikten **sonra**, `bekleyen`in denemeye başladığına dair bir onay
(`devam_al.recv()`) bekliyor ve ancak o onay geldikten sonra ikinci kaydını (`A-cikti`)
yazıp kilidi bırakıyor. Bu üç adımlı el sıkışma, `bekleyen`in kilit çağrısının gerçekten `A`
kilidi elinde tutarken yapıldığını garanti ediyor — `bekleyen` bu çağrıda bloklanıyor,
`tutan` kilidi bırakana kadar ilerleyemiyor. Bloklanma süresi ölçülmüyor, yalnızca sonucun
sırası: `B` girişleri hiçbir zaman `A` çıkışından önce görünmüyor.

#### İki Bekleyen: Sıra Ölçülmez

İkinci senaryoda `C` ve `D` aynı kilidi bekliyor. İkisi de sonunda giriyor — çıktı bunu
kanıtlıyor — ama **hangisinin önce** girdiği hiçbir yerde ölçülmüyor. Program bu belirsizliği
gizlemiyor, tersine yapısına gömüyor: kayıtlar toplanmadan önce sıralanıyor, tıpkı İş
Parçacığı Oluşturma dersindeki rapor kanalının yaptığı gibi. `girenler` listesi her koşumda
`["C-girdi", "D-girdi"]` olarak çıkıyor — bu, alfabetik sıralamanın sonucu, gerçek giriş
sırasının değil.

Bu senaryoda `C` ve `D` başlamadan önce günlüğe bir `"baslangic"` kaydı yazılıyor ve bu
kayıt raporlanan listede görünmüyor, çünkü `girenler` yalnızca `-girdi` ile biten satırları
süzüyor. Bu süzme bilinçli: `gunluk2` içinde tuttuğu kayıtların tam sırası aslında bellidir
ve deterministiktir (`baslangic` her zaman ilk sırada), ama `C-girdi` ile `D-girdi`
arasındaki göreli sıra bu kesinliği taşımıyor. Süzme, ölçülebilecek olanla ölçülemeyecek
olanı aynı veri yapısı içinde ayırıyor.

#### Okuma/Yazma Kilidi: Çok Okuyana İzin

Üçüncü senaryo bir dışlama kilidiyle bir okuma/yazma kilidi arasındaki farkı doğrudan
gösteriyor. `okuyucu1`, okuma kilidini alıp elinde tutarken bir sinyal gönderiyor;
`okuyucu2`, bu sinyali aldıktan **sonra** aynı değere kendi okuma kilidini istiyor. Eğer bu
kilit bir dışlama kilidi olsaydı, `okuyucu2` `okuyucu1` kilidi bırakana kadar bloklanırdı —
ve `okuyucu1`, `okuyucu2`nin bir onay göndermesini beklediği için, bu onay hiçbir zaman
gelmez ve program kilitlenirdi. Program kilitlenmeden tamamlandığına göre, `okuyucu2`nin
okuma kilidi çağrısı gerçekten `okuyucu1` hâlâ kilidi tutuyorken başarıyla dönmüş demektir.

Bu, ilk senaryodaki el sıkışmayla aynı mantığı tersine çeviriyor. Orada el sıkışma bir
bekleyişi kanıtlamak için kullanıldı: `bekleyen`in kilidi gerçekten `tutan` elindeyken
istediğini göstermek. Burada ise el sıkışma bir bekleyişin **yokluğunu** kanıtlamak için
kullanılıyor: eğer okuma çağrıları birbirini bloklasaydı, program bu el sıkışmayı hiçbir
zaman tamamlayamaz ve sonsuza kadar beklerdi. Programın normal biçimde bitmesi, kendi
başına okuma/yazma kilidinin okuma tarafının paylaşılabilir olduğunun kanıtı — bu derste
kullanılan ölçüm yöntemlerinden en dolaylısı, ama en kesini.

**IP7.** İki okuyucunun okuduğu değer birebir aynı (`42` ve `42`), çünkü ikisi de aynı
paylaşılan sayıyı okuyor ve okuma sırasında hiçbir yazma gerçekleşmiyor. Bu ölçüm, okuma/
yazma kilidinin okuma tarafının **kendi içinde** paylaşılabilir olduğunu gösteriyor: iki
okuyucu aynı anda kilide sahip olabiliyor, oysa aynı senaryo bir `Mutex` ile kurulsaydı
`okuyucu2` kendi kilidini isteyemeden `okuyucu1`in onayını asla gönderemeyeceği için program
kilitlenirdi.

Bu farkın kaynağı, okuma/yazma kilidinin iki ayrı giriş noktası sunması. Bir dışlama kilidi
tek bir çağrı taşır — kim çağırırsa çağırsın aynı kurala tabidir. Okuma/yazma kilidi ise okuma
ve yazma için ayrı çağrılar taşır: okuma çağrısı, kilitte hiçbir yazıcı yokken ne kadar
okuyucu isterse kabul eder; yazma çağrısı ise her zaman **tek başına** kalmayı ister, ne
başka bir yazıcıyla ne de bir okuyucuyla paylaşmaz. Bu ölçümde yalnızca okuma tarafı
koşturuldu; yazma çağrısının davranışı, dışlama kilidininkiyle özdeş — bir yazıcı kilide
girdiğinde, o an kilitte bekleyen her taraf (ister okuyucu ister yazıcı) onun bitmesini
bekler.

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

Kilit **veriyi** korur, **sırayı** kurmaz. Bir dışlama kilidi ya da bir okuma/yazma
kilidinin yazma tarafı, aynı anda yalnızca bir tarafın kritik bölgeye girmesini garanti
eder — bu derste ölçülen budur. Ama iki bekleyenden **hangisinin** önce gireceği, kilidin
kendi sözleşmesinin bir parçası değil; bu, işletim sisteminin zamanlayıcısına, kilidin iç
uygulamasına ve o anki yüke bağlı bir ayrıntıdır. `C` ve `D` örneğinde bu bilinçli olarak
ölçülmedi ve basılmadı — kayıtlar toplanmadan önce sıralanarak, hangi sıranın gerçekleştiği
sorusu programın çıktısından tamamen çıkarıldı.

Bu sınır, Muteks ve Okuma/Yazma Kilidi arasındaki farkı da netleştiriyor: okuma/yazma
kilidinin okuma tarafı birden fazla bekleyene aynı anda giriş **izni** veriyor, ama bu izin
de bir sıra garantisi taşımıyor — üçüncü bir okuyucu eklenmiş olsaydı, o okuyucunun ne zaman
gireceği yine ölçülmezdi. Kilit çözdüğü tek sorun, paylaşılan veriye erişimin ne zaman
**güvenli** olduğu; hangi tarafın önce erişeceği sorusunu hiç çözmüyor.

Bu ayrım rastgele bir seçim değil, kilidin verdiği garantinin doğal sınırı. Bir kilit, kaç
tarafın kritik bölgede aynı anda bulunabileceğini (dışlama kilidinde bir, okuma/yazma
kilidinin okuma tarafında sınırsız) tanımlıyor; kritik bölgeye giriş **sırasının** neye göre
belirleneceğini tanımlamıyor, çünkü bu genellikle işletim sistemi zamanlayıcısının kendi
politikasına bırakılan bir karar. Bu kursta hiçbir yerde bir zamanlayıcı politikasının adı
geçmiyor ve geçmeyecek; ölçülen yalnızca kilidin **verdiği** ve **vermediği** garantiler.

#### Özet

- Kilidi tutan taraf kendi başına ilerler; kilidi bekleyen taraf, kilit bırakılana kadar
  başkası bitince ilerler. Bu ilişki bu derste bir kanal el sıkışmasıyla belirlenimci
  biçimde kanıtlandı.
- İki bekleyen taraf arasında hangisinin önce gireceği ölçülmez ve basılmaz; kayıtlar
  toplanmadan önce sıralanarak bu belirsizlik programın çıktısından çıkarılır.
- Okuma/yazma kilidinin okuma tarafı, aynı anda birden fazla okuyucuya izin verir — bu,
  aynı senaryonun bir dışlama kilidiyle kilitlenmeye yol açacağı bir durumla karşılaştırıldı.
- Kilit paylaşılan veriye erişimi korur, ama hangi tarafın önce gireceğine dair hiçbir
  garanti vermez; bu iki şey birbirinden bağımsızdır.
- JVM, Eşzamanlılık ve Başarım kursu bir kilidi hangi iş parçacığının tuttuğunu ölçtü, bu
  kurs aynı kilidi bekleyen tarafın ilerlemek için neye ihtiyaç duyduğunu ölçüyor.

#### Sonraki Adım

Kilit bekleyen tarafı durduruyor, çünkü paylaşılan veriye erişim sırayla verilmek zorunda.
Bu derste görülen üç senaryonun hepsinde bir tarafın kilit için beklemesi gerekti — ya kilit
tutulduğu için (`bekleyen`, `C`, `D`), ya da bir yazma isteği söz konusu olsaydı (okuma/
yazma kilidinin yazma tarafı). Ama bazı paylaşımlar bu kadar sıkı bir korumaya ihtiyaç
duymuyor — özellikle paylaşılan değer tek bir sayı gibi basitse ve üzerinde yapılan işlem
donanımın kendisinin tek adımda gerçekleştirebileceği kadar küçükse. Sıradaki ders, hiç
kimsenin beklemediği bir paylaşım biçimini ölçüyor: bir sayacın kilitsiz, atomik işlemlerle,
kimseye ihtiyaç duymadan ilerlemesi — ve bunun kilitli bir sayaçla aynı sonucu verip
vermediği.

### Atomik İşlemler ve Bellek Sıralaması

Source: https://academia.sh/tr/kurslar/rust-eszamanlilik/atomik-islemler-ve-bellek-siralamasi

Kilitsiz bir sayaç beklemeden, kimseye ihtiyaç duymadan ilerler; aynı toplam artış hem dışlama kilidiyle hem atomik işlemlerle yapılır ve iki yöntem birebir aynı sonucu verir — fark sonuçta değil, bekleyenin olup olmamasındadır.

Önceki ders bir dışlama kilidini bekleyen tarafın nasıl ilerlediğini ölçtü: kilidi tutan
kendi başına, bekleyen başkası bitince. Bu dersin sorusu şu: her paylaşım bekleyen bir taraf
gerektiriyor mu? Yanıt hayır — belirli bir koşulu sağlayan basit paylaşımlar, hiç kimseyi
beklemeden, donanımın kendi garantisiyle güvenle güncellenebiliyor. Bu, İş Parçacıkları
konusunun son dersi ve kilitsiz ilerlemeyi doğrudan ölçüyor.

Bu koşul dar: yalnızca tek bir makine sözcüğüne sığan basit değerler (tam sayılar,
mantıksal değerler, işaretçi genişliğinde sayılar) atomik olarak güncellenebiliyor. Önceki
derslerde görülen `PaylasilanKayit` gibi birden fazla alan taşıyan bir yapı, ya da
Kanallarla Mesajlaşma dersindeki gibi öbekte büyüyen bir liste, tek bir atomik işlemle
güncellenemez — bu tip veriler hâlâ bir kilide ihtiyaç duyuyor. Bu dersin ölçtüğü kilitsiz
ilerleme, bu yüzden dar ama önemli bir özel durum: tek bir sayının, tek bir bayrağın, tek
bir işaretçinin paylaşılan güncellemesi.

#### Donanımın Tek Adımlık Garantisi

İşletim Sistemi Kavramları kursu ve JVM, Eşzamanlılık ve Başarım kursu karşılaştır-ve-değiştir
gibi donanım ilkellerinden söz etti; bu kavram burada yeniden kurulmuyor. Atomik bir işlem,
işlemcinin tek bir bölünmez adımda gerçekleştirdiği bir okuma-değiştirme-yazma dizisidir: bir
sayacı bir arttırmak, iki iş parçacığı aynı anda dener denese de, hiçbir zaman yarım kalmış
ya da üst üste binmiş bir sonuç üretmez. Bir dışlama kilidi de aynı sonucu sağlıyor, ama
farklı bir yolla — kilidi tutan tek taraf hariç herkesi bekletiyor. Atomik işlem ise hiç
kimseyi bekletmiyor; donanımın kendisi, aynı anda gelen iki isteği sıraya koyup ikisini de
kayıpsız işliyor.

Bu güvencenin neden gerekli olduğunu anlamak için, korumasız bir artışın neden yanlış
sonuç verdiğine bakmak yeterli. Bir tam sayıyı bir artırmak aslında üç ayrı adım: mevcut
değeri oku, bir ekle, sonucu geri yaz. İki iş parçacığı bu üç adımı aynı anda, korumasız
biçimde yapmaya çalışırsa, ikisi de aynı eski değeri okuyabilir, ikisi de kendi hesapladığı
yeni değeri yazabilir — ve son yazan, öbürünün artışını **örter**. Sonuç, iki artış
yapılmış olmasına rağmen sayacın yalnızca bir artmış olmasıdır; bu bir veri yarışının
doğrudan sonucu. Atomik bir işlem bu üç adımı donanım düzeyinde tek bir bölünmez birim
hâline getirerek bu örtmeyi imkânsız kılıyor — okuma, ekleme ve yazma arasına hiçbir başka
iş parçacığı giremiyor.

Bu farkın koşumdaki karşılığı şu: bir dışlama kilidiyle korunan bir sayacı arttıran on iş
parçacığından dokuzu, herhangi bir anda dokuzuncusu kilidi tutarken bekliyor olabilir —
başkası bitince sınıfına giriyorlar. Atomik bir sayacı arttıran on iş parçacığının hiçbiri
böyle bir bekleyişe girmiyor; hepsi kendi başına sınıfında kalıyor. Aşağıdaki ölçüm bu
farkı, iki yöntemin ürettiği **sonucu** karşılaştırarak gösteriyor: ikisi de aynı toplamı
üretiyor, çünkü ikisi de kayıp güncelleme olmadan çalışıyor — farklı olan yalnızca bunu nasıl
başardıkları.

İş Parçacıkları Arası Güvenliğin Tipteki Karşılığı dersinde kurulan `Send`/`Sync` çerçevesi
burada da geçerli: `AtomicUsize` tipi hem `Send` hem `Sync`dir, bu yüzden `Arc<AtomicUsize>`
birden fazla iş parçacığı arasında paylaşılabiliyor. Bu, `Arc<Mutex<usize>>`in taşıdığı
aynı iki izinle aynı — aradaki fark, `Mutex`in kendi içinde bir kilit taşıması, `AtomicUsize`
tipinin ise hiçbir kilit taşımadan, doğrudan donanımın bölünmez işlemlerine dayanmasıdır.
İkisi de tip sisteminde aynı iznin bir örneği, ama garantiyi sağlama biçimleri farklı.

#### İki Yöntem, Aynı Sonuç

Aşağıdaki program iki işlev tanımlıyor ve `main` içinde ikisini de aynı sabitlerle
çalıştırıyor: on iş parçacığı, her biri beş yüz kez artış. `kilitle_ve_say` bunu bildiğimiz
`Arc<Mutex<usize>>` düzeneğiyle yapıyor — her artış bir tutamaç istiyor, tutamaç kapsam
dışına çıkınca kilit bırakılıyor. `atomik_say` aynı işi `Arc<AtomicUsize>` ile yapıyor;
burada tutamaç yok, yalnızca `fetch_add` adlı tek bir çağrı var ve bu çağrı hem okuma hem
yazma işini kendi içinde, bölünmez biçimde yapıyor. İki işlevin de dönüş tipi aynı
(`usize`), böylece sonuçları doğrudan karşılaştırılabiliyor.

```rust
// atomik.rs — kilitsiz bir sayac beklemeden ilerler; artis sirasi olculmez, yalniz toplam.

use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;

const IS_PARCACIGI: usize = 10;
const ARTIS: usize = 500;

fn kilitle_ve_say() -> usize {
    let sayac = Arc::new(Mutex::new(0usize));
    let mut tutamaclar = Vec::new();
    for _ in 0..IS_PARCACIGI {
        let s = Arc::clone(&sayac);
        tutamaclar.push(thread::spawn(move || {
            for _ in 0..ARTIS {
                // her artis kilidi ister: bekleyen bir is parcacigi baskasi bitince ilerler
                let mut deger = s.lock().unwrap();
                *deger += 1;
            }
        }));
    }
    for t in tutamaclar {
        t.join().unwrap();
    }
    let sonuc = *sayac.lock().unwrap();
    sonuc
}

fn atomik_say() -> usize {
    let sayac = Arc::new(AtomicUsize::new(0));
    let mut tutamaclar = Vec::new();
    for _ in 0..IS_PARCACIGI {
        let s = Arc::clone(&sayac);
        tutamaclar.push(thread::spawn(move || {
            for _ in 0..ARTIS {
                // hicbir is parcacigi kilit beklemez: her artis kendi basina ilerler
                s.fetch_add(1, Ordering::SeqCst);
            }
        }));
    }
    for t in tutamaclar {
        t.join().unwrap();
    }
    sayac.load(Ordering::SeqCst)
}

fn main() {
    let kilitli_sonuc = kilitle_ve_say();
    let atomik_sonuc = atomik_say();
    let beklenen = IS_PARCACIGI * ARTIS;

    println!("beklenen toplam: {}", beklenen);
    println!("kilitle sayilan toplam: {}", kilitli_sonuc);
    println!("atomik sayilan toplam: {}", atomik_sonuc);
    println!(
        "iki yontem ayni sonucu verdi: {}",
        kilitli_sonuc == atomik_sonuc && atomik_sonuc == beklenen
    );

    // --- iki atomik sayac: hangi artisin once oldugu olculmuyor, yalniz nihai toplam ---
    let a = Arc::new(AtomicUsize::new(0));
    let b = Arc::new(AtomicUsize::new(0));
    let mut tutamaclar = Vec::new();
    for i in 0..IS_PARCACIGI {
        let (a, b) = (Arc::clone(&a), Arc::clone(&b));
        tutamaclar.push(thread::spawn(move || {
            if i % 2 == 0 {
                a.fetch_add(1, Ordering::SeqCst);
            } else {
                b.fetch_add(1, Ordering::SeqCst);
            }
        }));
    }
    for t in tutamaclar {
        t.join().unwrap();
    }
    let toplam_ikili = a.load(Ordering::SeqCst) + b.load(Ordering::SeqCst);
    println!();
    println!("iki ayri atomik sayacin toplami: {}", toplam_ikili);
    println!("(hangi is parcaciginin hangi sayaci once arttirdigi basilmiyor)");
}
```

```
beklenen toplam: 5000
kilitle sayilan toplam: 5000
atomik sayilan toplam: 5000
iki yontem ayni sonucu verdi: true

iki ayri atomik sayacin toplami: 10
(hangi is parcaciginin hangi sayaci once arttirdigi basilmiyor)
```

**IP8.** On iş parçacığı, her biri beş yüz kez artırarak, aynı sayacı beş bin defa
arttırıyor. `kilitle_ve_say` bunu bir dışlama kilidiyle yapıyor: her artış `lock` çağrısı
istiyor ve on iş parçacığından dokuzu herhangi bir anda kilit için bekleyebiliyor —
başkası bitince sınıfına giren bir düzenek. `atomik_say` aynı beş bin artışı `fetch_add`
çağrısıyla yapıyor: hiçbir iş parçacığı bir kilide ihtiyaç duymuyor, hepsi kendi başına
sınıfında kalıyor. İki yöntem de **beklenen** toplamı (5000) üretiyor ve bu sonuç üç ayrı
koşumda birebir aynı çıkıyor — kilitli ve atomik sayaçlar arasında hiçbir kayıp güncelleme
farkı yok.

İki işlevin yapısı da bunu görünür kılıyor: `kilitle_ve_say` içindeki döngü gövdesi bir
`Mutex` tutamacı alıyor, değeri değiştiriyor ve tutamacı bırakıyor — bu üç adım her artışta
tekrarlanıyor. `atomik_say` içindeki döngü gövdesi ise yalnızca `fetch_add` çağırıyor; ne
bir tutamaç alınıyor ne de açıkça bırakılıyor, çünkü `AtomicUsize` tipinin kendisi bir kilit
taşımıyor. İki işlevin de aynı sayıda iş parçacığı, aynı sayıda artış kullanması ve aynı
`beklenen` sabitle karşılaştırılması, tek değişkenin **yöntem** olmasını sağlıyor — sonuçtaki
her fark, kilitli olup olmamaktan kaynaklanıyor, başka hiçbir değişkenden değil.

#### Sıralama Ölçülmez, Yalnızca Toplam

Programın ikinci bölümü iki ayrı atomik sayaç kullanıyor: çift numaralı iş parçacıkları `a`
sayacını, tek numaralı iş parçacıkları `b` sayacını artırıyor. On iş parçacığının hangi
sırayla çalıştığı, hangisinin önce `fetch_add` çağırdığı bu programda hiçbir yerde
ölçülmüyor ve basılmıyor — yalnızca iki sayacın nihai toplamı (`10`) yazdırılıyor. Bu, İş
Parçacığı Oluşturma dersinde kurulan kuralın burada da geçerli olduğunu gösteriyor:
zamanlamaya bağlı hiçbir gözlem basılmıyor, yalnızca belirlenimci olan — bu örnekte, kaç
iş parçacığının çift ya da tek numaralı olduğu, dolayısıyla nihai toplamın ne olacağı —
basılıyor.

Bu ikinci ölçümün amacı, tek bir sayaç yerine iki ayrı sayaç kullanarak, "hangi iş
parçacığı önce çalıştı" sorusunun cevabının programın **çıktısını değiştirmediğini**
göstermek. On iş parçacığının zamanlayıcı tarafından hangi sırayla çalıştırıldığı her
koşumda farklı olabilir — bu programda `IS_PARCACIGI` sabiti on olduğu için beş tanesi `a`
sayacına, beş tanesi `b` sayacına yazıyor ve iki sayacın toplamı her zaman on. Zamanlama
değişse bile bu toplam değişmiyor, çünkü `i % 2` hesabı iş parçacığının **numarasına**
bağlı, çalıştırılma **sırasına** değil. Bu, bir hesabın sonucu ile o hesabı üreten
işlemlerin sırası arasındaki farkı gösteren bir başka örnek.

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

Atomik olmak sıralı olmak anlamına gelmiyor. Bu derste kullanılan `Ordering::SeqCst`,
işlemin kendisinin bölünmez olduğunu garanti ediyor — hiçbir iş parçacığı yarım kalmış bir
artışı göremiyor — ama **hangi** artışın diğerinden önce gerçekleştiğini bu koşumda hiçbir
yerde belirlemiyoruz ve belirleyemeyiz de: on iş parçacığı aynı anda çalışıyor ve
zamanlayıcının onlara işlemci payını hangi sırayla vereceği ortama bağlı. Ölçülen tek şey,
beş bin artışın **hiçbirinin kaybolmadığı** — final toplamın tam olarak beklenen sayıya
eşit olduğu. Hangi iş parçacığının hangi anda kaçıncı artışı yaptığı sorusu, bu kursun
belirlenimcilik kuralı gereği hiçbir yerde ölçülmüyor.

Bellek sıralamasının kendisi — `Ordering::SeqCst`, `Ordering::Relaxed` ve aradaki
seçenekler — bu derste **kavram olarak** anlatılıyor, ölçülmüyor, çünkü ölçülemiyor: bir
sıralama seçeneğinin diğerinden farkı, çok işlemcili bir donanımın kendi önbellek
tutarlılık davranışında ortaya çıkıyor ve bu davranış koşumdan koşuma, hatta aynı koşum
içinde çağrıdan çağrıya değişebiliyor. `SeqCst` en güçlü ve en kolay akıl yürütülen
seçenektir: bütün iş parçacıkları, bütün atomik işlemleri aynı global sırada görüyormuş gibi
davranır. Daha gevşek seçenekler (`Relaxed` gibi) yalnızca tek bir değişkenin bölünmezliğini
garanti eder, işlemler arasındaki görünürlük sırasını garanti etmez — bu fark, iki farklı
atomik değişken arasında bir ilişki kurulmak istendiğinde önem kazanır, ama bu derste
kullanılan tek bir sayaç senaryosunda görünmez.

Neden `SeqCst` seçildiği de bu sınırın bir parçası. Bu ders tek bir atomik değişkeni (ve
ikinci ölçümde, birbirinden bağımsız iki atomik değişkeni) arttırıyor; iki farklı atomik
değişken arasında bir okumanın diğerinin yazmasından **önce mi sonra mı** göründüğünü
gözlemleyen bir senaryo burada kurulmuyor. Böyle bir senaryo kurulsaydı, seçilen sıralama
seçeneği sonucu etkileyebilirdi ve o fark yalnızca belirli işlemci mimarilerinde, belirli
önbellek durumlarında ortaya çıkardı — yani ortama bağlı bir gözlem olurdu. Bu kursun
kuralı gereği böyle bir gözlem hiç kurulmuyor; `SeqCst` en güvenli seçenek olduğu için
seçildi ve dersin ölçtüğü tek şey — toplamın doğruluğu — açısından hiçbir sıralama
seçeneği bir diğerinden farklı sonuç vermezdi.

#### Özet

- Atomik bir işlem, donanımın tek bir bölünmez adımda gerçekleştirdiği bir okuma-değiştirme-
  yazma dizisidir; kilitsiz bir sayaç bu sayede kimseye ihtiyaç duymadan, kendi başına
  ilerler.
- Aynı beş bin artış hem kilitli hem atomik yöntemle yapıldığında birebir aynı toplamı
  (5000) üretiyor; fark sonuçta değil, kilitli sayaçta bekleyen bir tarafın bulunması,
  atomik sayaçta ise hiç bulunmamasında.
- Kilitli bir sayaçta iş parçacıkları kilit için bekleyerek başkası bitince sınıfına
  girebilir; atomik bir sayaçta hiçbir iş parçacığı kimseyi beklemez, hepsi kendi başına
  ilerler.
- Atomik olmak sıralı olmak değildir: hangi artışın önce gerçekleştiği bu kursta hiç
  ölçülmez ve basılmaz; ölçülen yalnızca nihai toplamın doğruluğudur.
- Bellek sıralaması seçenekleri kavram olarak anlatılır, koşumla ölçülmez, çünkü
  aralarındaki fark donanımın önbellek davranışına bağlıdır ve bu davranış basılmaz.
- Kilitsiz ilerleme dar bir koşula bağlıdır: yalnızca tek bir makine sözcüğüne sığan basit
  değerler atomik olarak güncellenebilir; birden fazla alan taşıyan bir yapı hâlâ bir
  kilide ihtiyaç duyar.

#### Sonraki Adım

Bu konunun beş dersi boyunca ölçülen birimler kursun dört sınıfından üçüne düştü: kendi
başına ilerleyen bağımsız iş parçacıkları — bu derste görülen kilitsiz sayaçlar da bu sınıfta
—, başkasının bitmesini bekleyen taraflar ve kaynakta yazılı olduğu hâlde hiç ilerlemeyen
birimler. İlerleyenlerin hepsi ortak bir varsayıma dayanıyordu: bir iş parçacığı,
başlatıldığı andan itibaren işletim sisteminin zamanlayıcısı tarafından ileriye taşınıyor —
programcı yalnızca ne zaman **bekleyeceğini** belirliyor, ilerlemenin kendisini değil.
Dördüncü sınıf, `yoklanınca`, bu konuda hiç görülmedi.

Eşzamansız Programlama konusu bu varsayımı tersine çeviriyor. Kaynakta yazılan bir görev,
hiçbir iş parçacığına bağlanmadan, kendiliğinden hiç ilerlemeyen tembel bir değer olarak
duruyor — bir iş parçacığının aksine, bir görev başlatıldığı anda hiçbir şey yapmıyor.
Sıradaki ders bu farkı doğrudan ölçüyor: aynı kaynaktan kurulan iki görevden biri bir
yürütücüye verilir ve ilerler, öbürü verilmez ve hiçbir kayıt üretmez — kaynakları birebir
aynı olsa da.

## Eşzamansız Programlama

Bir görevin kendiliğinden ilerlemeyen tembel bir değer olması, duraklama noktalarının kaynakta görünür yazılması, görevleri ilerleten yürütücünün dilin dışında kalması, girdi çıktı beklerken duraklamanın anlamı, ve bir görevin bırakılmasıyla kurulan iptal.

### Gelecekler (futures)

Source: https://academia.sh/tr/kurslar/rust-eszamanlilik/gelecekler

Bir gelecek, kurulduğu anda değil yalnız yoklandığında ilerleyen tembel bir değerdir: aynı kaynaktan kurulan iki görevden yürütücüye verilen bir kaydı deftere yazarken, verilmeyen hiçbir iz bırakmaz.

İş Parçacıkları konusu paylaşılan durumu bir kilitle ya da atomik bir işlemle koruyan iş
parçacıklarını ölçtü; ikisinde de birim bir kez başlatıldıktan sonra ya kendi başına
ilerliyordu ya da bir başkasının bitmesini bekliyordu. Bu konu aynı soruyu — bir iş ilerlemek
için neye ihtiyaç duyuyor — başka bir birime taşıyor: eşzamansız görev. İlk bakışta yanıt
tanıdık görünüyor, kendi başına ya da başkası bitince. Ama Rust'ın eşzamansız modeli üçüncü
bir olasılığı gündeme getiriyor ve bu ders tam olarak onu kuruyor: bir görev, kaynakta
yazılmış olmasına rağmen hiç ilerlemeyebilir.

Bir iş parçacığı, işletim sisteminin ayırdığı bir yürütme birimidir: `thread::spawn`
çağrıldığı anda kendi yığıtı ve zamanlanma hakkı olan bir şey doğar, çağıran kod bir daha
elini sürmese bile ilerlemeye devam eder. Bir eşzamansız görev bunun tam tersi bir şeyi
temsil ediyor — kendi başına hiçbir yürütme bağlamı taşımayan, salt bir veri değeri. Bir
`HemenHazir` örneği kurulduğunda ne bir yığıt ayrılıyor ne de bir zamanlanma hakkı doğuyor;
ortada yalnız birkaç alan taşıyan sıradan bir yapı örneği var. Bu farkı görmek, kursun geri
kalanı için belirleyici: iş parçacıkları konusu ilerlemeyi işletim sisteminin bir kararı
olarak ölçtü, bu konu ilerlemeyi **kaynaktaki bir kod parçasının kararı** olarak ölçüyor.

Rust'ta bir eşzamansız görev, standart kitaplığın `Future` trait'ini gerçekleştiren bir
değerdir. Bu trait tek bir zorunlu yöntem bildirir: `poll`. Yöntem bir kez çağrıldığında iki
şeyden birini döndürür — görev bitmişse taşıdığı değeri saran `Ready`, bitmemişse `Pending`.
`Future` trait'i kavram olarak Rust'a özgü değil; farkı, bu kararı **kimin verdiğidir**.
Değeri üreten kod kendi kendine ilerlemez, ilerlemesi için dışarıdan bir çağrının `poll`u
tetiklemesi gerekir. Bu çağrıyı yapan bileşene **yürütücü** denir ve bu konunun üçüncü dersi
doğrudan ona ayrılmıştır; burada yürütücü yalnız görevi ilerletmek için gereken bir araç
olarak kullanılıyor, kendisi bu dersin konusu değil.

Bu tembellik rastgele bir gerçekleştirim ayrıntısı değil, `Future` trait'inin tasarımının
merkezinde duran bir karardır. Programlama Temelleri kursunda tanıtılan tembel değerlendirme
kavramı, bir ifadenin değerinin yalnız gerçekten ihtiyaç duyulduğunda hesaplanmasını
anlatıyordu; Rust'ın gelecekleri aynı ilkeyi eşzamansız işe uyguluyor. Bir gelecek kurmak
yalnız bir **tarif** yazmaktır — hangi adımların hangi sırayla atılacağının bir kaydı. Bu
tarifin gerçekten yürütülmesi, ayrı ve açık bir kararla, yürütücüye teslim edilerek başlıyor.
Ayrım önemli çünkü aksi halde çok sayıda geleceği aynı anda kurmak bile pahalı olurdu: her
biri kurulduğu anda çalışmaya başlasaydı, hangi görevin öncelikli olduğuna karar vermeden
önce hepsi zaten yarı yolda olurdu. Tembellik, kurma ile çalıştırma arasına bir ayrım
koyuyor; bu ayrım kursun kapanış dersinde bırakılan bir görevin bir daha hiç yoklanmamasının
neden mümkün olduğunu da açıklıyor — kaynakta durup hiç ilerlememiş bir gelecek, zaten bu
dersteki `_hic_yoklanmayan` ile aynı durumda.

#### Bir Değerin Kurulması Onu İlerletmez

`Future` gerçekleştiren bir tipin örneğini kurmak, sıradan bir yapı örneği kurmaktan farklı
görünmüyor: alanlar dolduruluyor, değer bir değişkene bağlanıyor. Fark, bu kurulma anında
hiçbir şeyin **çalışmamasıdır**. Aşağıdaki kesit, hazır olduğunda hemen tamamlanan en yalın
gelecek türünü ve onu ilerletmek için gereken en yalın yürütücüyü tanımlıyor.

```rust
// ilerle.rs — cekirdegin kesiti: elle yazilan yurutucu ve hemen hazir olan bir gelecek.

use std::future::Future;
use std::pin::Pin;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};

pub struct Uyandirici {
    pub hazir: Mutex<bool>,
    pub kosul: Condvar,
}

impl Wake for Uyandirici {
    fn wake(self: Arc<Self>) {
        *self.hazir.lock().unwrap() = true;
        self.kosul.notify_one();
    }
}

pub fn kostur<F: Future>(gorev: F) -> (F::Output, u32) {
    let mut gorev = Box::pin(gorev);
    let u = Arc::new(Uyandirici {
        hazir: Mutex::new(false),
        kosul: Condvar::new(),
    });
    let waker: Waker = u.clone().into();
    let mut baglam = Context::from_waker(&waker);
    let mut yoklama = 0;
    loop {
        yoklama += 1;
        match gorev.as_mut().poll(&mut baglam) {
            Poll::Ready(x) => return (x, yoklama),
            Poll::Pending => {
                let mut h = u.hazir.lock().unwrap();
                while !*h {
                    h = u.kosul.wait(h).unwrap();
                }
                *h = false;
            }
        }
    }
}

pub struct HemenHazir {
    pub ad: &'static str,
    pub kanal: Sender<String>,
}

impl Future for HemenHazir {
    type Output = &'static str;
    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<&'static str> {
        self.kanal.send(self.ad.to_string()).unwrap();
        Poll::Ready(self.ad)
    }
}
```

`kostur` işlevi, Rust'ın standart kitaplığında bulunmayan bir parçayı elle kuruyor: bir
`Waker`, bir `Context` ve `poll`u tekrar tekrar çağıran bir döngü. Yürütücünün gövdesi bu
derste yalnız bir araç olarak veriliyor; asıl ölçülen, `HemenHazir` tipinin kendisi. `poll`
çağrılmadan önce bu tipin örneği bellekte duran sıradan bir değerden farksız — hiçbir alanı
çalışmıyor, hiçbir kanala bir şey göndermiyor. Yalnız `poll` çağrıldığında `kanal.send`
tetikleniyor ve değer üretiliyor.

`kostur`un döngüsü üç adımı tekrarlıyor: yoklama sayacını artırmak, `poll`u çağırmak, sonucu
değerlendirmek. `Ready` dönerse döngü biter ve değerle birlikte toplam yoklama sayısı geri
veriliyor. `Pending` dönerse döngü, `Uyandirici`nin taşıdığı mantıksal bayrağı `false`
bulduğu sürece bir koşul değişkeninde bekliyor — bu, iş parçacığını meşgul bir döngüde
döndürmek yerine gerçekten uyutan, Muteks ve Okuma/Yazma Kilidi dersinde tanıtılan kilitleme
ilkelleriyle aynı ailenin bir üyesi. `while` döngüsü tek bir `if` yerine kullanılıyor çünkü bir
koşul değişkeni kendisine hiç uyandırma çağrısı gelmeden de uyanabilir; bayrağı tekrar kontrol
etmek bu olası yanlış uyanışı görmezden geliyor. `Waker`, bir `Arc<Uyandirici>`nin kopyası
olarak taşınıyor — bir görev tek bir koşumda birden fazla kez `Pending` dönebildiği için, aynı
`Uyandirici` birden fazla `poll` çağrısı arasında paylaşılabiliyor olmalı.

Aşağıdaki program aynı kaynaktan — aynı `HemenHazir` tanımından — iki görev kuruyor. Biri
`kostur`a veriliyor, öbürü yalnız bir değişkene bağlanıp bir daha hiç dokunulmuyor.

```rust
// tembel.rs — ayni kaynaktan iki gorev: biri yurutucuye verilir, oburu hic dokunulmaz.
mod ilerle;
use ilerle::{kostur, HemenHazir};
use std::sync::mpsc::channel;

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

    // ayni kaynaktan (HemenHazir tanimindan) iki ayri gorev kuruluyor
    let yoklanan = HemenHazir {
        ad: "gorev-yoklanan",
        kanal: gonder.clone(),
    };
    let _hic_yoklanmayan = HemenHazir {
        ad: "gorev-hic-yoklanmayan",
        kanal: gonder.clone(),
    };

    // yalniz biri yurutucuye veriliyor
    let (deger, yoklama) = kostur(yoklanan);

    drop(gonder);
    let kayitlar: Vec<String> = al.try_iter().collect();

    println!("donen deger: {}", deger);
    println!("yoklama sayisi: {}", yoklama);
    println!("kayit sayisi: {}", kayitlar.len());
    for k in &kayitlar {
        println!("  {}", k);
    }
}
```

```
donen deger: gorev-yoklanan
yoklama sayisi: 1
kayit sayisi: 1
  gorev-yoklanan
```

**AS1.** Defterde tek bir kayıt var. `_hic_yoklanmayan` değişkeni kaynakta yazılı, tipi
`HemenHazir`, alanları dolu — ama karşılığında hiçbir satır basılmadı, çünkü `poll` hiç
çağrılmadı. Bu iki görev arasındaki tek fark yürütücüye verilip verilmediği; kaynak kodları,
tipleri ve alan değerleri birebir aynı yapıdan geliyor. Kayıt sayısı beş ayrı koşumda birebir
aynı kaldı: bu bir yarış değil, bir davranış.

Konunun paylaştığı ortak ölçüm çekirdeği on üç birimi aynı dört sınıfa ayırıyor: beşi kendi
başına, üçü yoklanınca, üçü başkası bitince ilerliyor, ikisi hiç ilerlemiyor. Bu dersteki iki
birim — `gorev-yoklanan` ve `gorev-hic-yoklanmayan` — o on üçlünün yoklanınca ve hiç
sınıflarından birer örnek; kalan sınıflar İş Parçacıkları konusunda ve bir sonraki derste ele
alınıyor.

Bu, kursun sorduğu sorunun ilk yanıtı. `gorev-yoklanan` **yoklanınca** ilerledi:
kendiliğinden değil, `kostur`un onu çağırmasıyla. `gorev-hic-yoklanmayan` ise **hiç**
ilerlemedi — kaynakta durdu, hiçbir zaman `poll` almadı. Trait'ler, Genellikler ve Hata
Yönetimi kursu bir gövdenin **kaç kez koştuğunu** saymıştı — her seferinde, yalnız
gerektiğinde ya da hiç. Bu kurs aynı üçlüden farklı bir soru soruyor: kaç kez koştuğu değil,
koşması için **kimin** onu ilerlettiği. `gorev-yoklanan`ı ilerleten özne `kostur`; `HemenHazir`
tipinin kendisi bu konuda hiçbir karar vermiyor, yalnız çağrıldığında ne yapacağını söylüyor.

#### Bir Görev de bir Değer Üretir, Sıradan bir Çağrı Değildir

Sıradan bir fonksiyon çağrısı (`f(x)`) çağrıldığı anda çalışır; dönüş değeri geri gelene kadar
çağıran taraf bekler. `HemenHazir`in `poll` yöntemi de benzer görünüyor — çağrıldığı anda
sonucunu veriyor — ama bu, `Future` trait'inin genel kuralı değil, `HemenHazir`e özgü bir
durum. Genel kural şu: bir gelecek, `poll` çağrıldığında ya biter (`Ready`) ya da **henüz
bitmediğini söyler** (`Pending`) ve kendini bir sonraki `poll` çağrısına bırakır. `HemenHazir`
her zaman ilk çağrıda bitiyor çünkü beklediği hiçbir şey yok; bir sonraki derste kurulacak
gelecek türü ise en az bir kez `Pending` dönüyor. Bu iki uç, aynı trait'in iki farklı
gerçekleştirimi — sözleşme aynı, davranış farklı.

`poll`un imzası da bu farkı taşıyor:
`fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>`. `Context`,
yürütücünün geleceğe verdiği tek kanal — içinde bir `Waker` taşıyor ve gelecek, kendini bir
sonraki `poll` için hazır saydığında bu `Waker`i çağırıp yürütücüye haber verebiliyor.
`HemenHazir` bunu hiç kullanmıyor çünkü hiçbir zaman `Pending` dönmüyor; sonraki ders bu
kanalı doğrudan kullanan bir gelecek kuruyor.

`Waker`in kendisi de tembel bir sözleşme taşıyor: uyandırma çağrısı yapıldığında hiçbir kod
anında çalışmıyor, yalnız yürütücüye "bu görevi tekrar yokla" diye bir işaret bırakılıyor.
`kostur`daki `Uyandirici` tipi bu işareti bir mantıksal bayrak ve bir koşul değişkeniyle
tutuyor: uyandırma çağrısı geldiğinde bayrağı `true` yapıp bekleyen döngüyü uyandırıyor. Bir
gelecek `Pending` döndürüp bu çağrıyı hiç yapmazsa, yürütücü sonsuza kadar bekler — bu,
geleceğin kendi sorumluluğu, yürütücünün değil. `HemenHazir` bu riski taşımıyor çünkü hiçbir
zaman `Pending` dönmüyor; bir sonraki derste kurulacak gelecek türü, her `Pending`
dönüşünde uyandırma çağrısını yaparak bu sözleşmeyi doğru yerine getiriyor.

`kostur`, geleceği `Box::pin` ile öbeğe yerleştiriyor. Bunun nedeni `HemenHazir`in kendisinde
yok — bu basit tip yer değiştirdiğinde hiçbir sorun çıkarmaz. Neden, bir sonraki derste
kurulacak `async fn` gövdelerinde ortaya çıkıyor: derleyici, bir `async fn`i kendi içinde
başka bir geleceği bekleyen bir durum makinesine çeviriyor ve bu makine kendi alanlarına
işaret eden referanslar taşıyabiliyor. Böyle bir değer bellekte yer değiştirirse taşıdığı
referanslar geçersiz kalır; `Pin`, bir değerin adresinin `poll` çağrıları arasında sabit
kalacağını derleyiciye söyleyen bir garantidir. `HemenHazir` bu garantiye ihtiyaç duymuyor
ama `kostur` her `Future` için aynı yolu izliyor, çünkü hangi geleceğin kendine referans
taşıyıp taşımadığını genel bir işlev bilemez. Bir sonraki derste tanıtılacak `async` yazımı da
tembellik kuralına uyuyor: eşzamansız bir fonksiyonu çağırmak onun gövdesini çalıştırmaz,
yalnız bir gelecek değeri üretir — gövdenin gerçekten çalışması, tıpkı burada `HemenHazir`de
olduğu gibi, o değerin yoklanmasını bekler.

Dikkat edilmesi gereken bir ayrıntı daha var: `kostur`un kendisi eşzamansız değil, sıradan
bir fonksiyon çağrısı. `main` içindeki `kostur(yoklanan)` satırı çalıştığında, çağıran iş
parçacığı gelecek bitene kadar bu çağrıda kalıyor — tıpkı İş Parçacıkları konusunda
birleştirmeyi bekleyen ana iş parçacığı gibi. Eşzamansızlık burada iş parçacığı sayısını
azaltmıyor; aynı iş parçacığı üzerinde birden fazla geleceği sırayla ya da iç içe ilerletmenin
bir yolunu açıyor. Bu ayrım, ilerleyen derslerde birden fazla görevin aynı yürütücü üzerinde
nasıl bir arada durabildiğini anlamanın temelini kuruyor.

#### `poll` Doğrudan da Çağrılabilir

`kostur`, `poll`u bir döngü içinde çağırıyor, ama bu çağrı biçimi zorunlu değil — `poll`
sıradan bir yöntemdir ve elinde geçerli bir `Context` olan her kod onu çağırabilir.
`yoklanan.poll(&mut baglam)` `kostur`un yerine doğrudan yazılsaydı, dönüş değeri yine
`Poll::Ready("gorev-yoklanan")` olurdu — çünkü `HemenHazir` ilk çağrıda bitiyor, döngünün
kaç kez çalıştığı onun için önemsiz. Yürütücünün asıl işi bu örnekte görünmüyor: tek bir
çağrı yeten bir gelecekte yürütücü yalnızca bir aracı. Fark, bir sonraki derste kurulacak,
birden fazla `Pending` dönen gelecek türünde ortaya çıkacak — orada `poll`u ne zaman tekrar
çağıracağını bilmek, elle yazılan koddan yürütücünün sorumluluğuna geçiyor. Bu kursun sorduğu
soru tam olarak burada devreye giriyor: bir görevi ilerleten şey kendi mantığı değil, onu
çağıran kod.

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

Yoklanmayan bir görev bir **hata değildir**. `_hic_yoklanmayan` derleniyor, bellekte bir
değişken olarak duruyor, kapsamı bitince bırakılıyor — ve bu sırada hiçbir hata, hiçbir uyarı
üretmiyor. Derleyici, bir `Future`in gerçekten yoklanıp yoklanmadığını izlemiyor; bunu izlemek
programcının ya da yürütücünün işi. Bu, kursun ortama bağlı veri yasağından farklı bir tür
sessizlik: burada eksik olan bir kayıt değil, hiç talep edilmemiş bir ilerlemedir. Kodun kendi
davranışı, "hiç" sınıfının neden ayrı bir hata biçimi olmadığını gösteriyor — kaynakta yazılı
bir şey, çalıştırılmadığı sürece hiçbir iz bırakmadan var olabiliyor.

Bu dersteki "hiç" örneği, konunun paylaştığı ortak ölçüm çekirdeğindeki iki "hiç" biriminden
biriyle aynı ailede: biri hiç başlatılmayan bir kapanış, öbürü hiç yoklanmayan bir görev —
ikisi de kaynakta yazılı, ikisi de derlendi, ikisi de hiçbir iz bırakmadı. İş Parçacıkları
konusunda bir birim başlatılmadan kaldığında da aynı sessizlik gözlemleniyordu; farkı, orada
sorumlu `thread::spawn`ın hiç çağrılmaması, burada `poll`un hiç çağrılmamasıydı. İkisi de aynı
kurala bağlanıyor: kaynakta yazılı olmak, ilerlemiş olmak anlamına gelmiyor.

#### Özet

- Bir gelecek, `Future` trait'ini gerçekleştiren bir değerdir; tek zorunlu yöntemi `poll`,
  dönüşü `Ready` (bitti) ya da `Pending` (henüz bitmedi).
- Bir geleceğin kurulması hiçbir şeyi çalıştırmaz — yalnız `poll` çağrısı ilerletir. Bu, bir
  görevin **yoklanınca** sınıfına girmesinin temelidir.
- Aynı kaynaktan kurulan iki görevden yürütücüye verilmeyeni **hiç** ilerlemedi: kaynakta
  yazılıydı, derlendi, ama hiçbir kayıt üretmedi.
- Bir görevi ilerleten özne yürütücüdür, görevin kendisi değil — bu, bir gövdenin kaç kez
  koştuğunu sayan Trait'ler, Genellikler ve Hata Yönetimi kursundan farklı bir eksendir.
- `Context` ve `Waker`, bir geleceğin yürütücüye "hazır olduğumda haber ver" demesini sağlayan
  kanaldır; bu ders onu tanıttı, kullanımı bir sonraki derste.
- Bir iş parçacığı işletim sisteminin ayırdığı bir yürütme birimidir, bir gelecek ise salt bir
  veri değeridir — kendi başına hiçbir yürütme bağlamı taşımaz, yalnız onu yoklayan yürütücü
  çağrıldığı sürece ilerler.

#### Sonraki Adım

Bu ders bir görevin kurulmasıyla ilerlemesi arasındaki farkı `HemenHazir` üzerinden gösterdi
— hiç durmayan, ilk çağrıda biten bir gelecek. Bu, mümkün en yalın gelecek türüydü: tek bir
`poll` çağrısı yetiyor, `Pending` hiç dönmüyor. Sıradaki ders aynı sözleşmeyi bir kez duran
bir gelecekle genişletiyor: duraklama noktası kaynakta nasıl görünür oluyor, `async`/`await`
yazımı bu noktaları nasıl kuruyor ve bir görevi bekleyen başka bir görev hangi sınıfa
giriyor.

### `async`/`await`

Source: https://academia.sh/tr/kurslar/rust-eszamanlilik/async-await

Duraklama noktaları kaynakta görünür yazılır: bir kez duraklayan bir gövde iki yoklamada, hemen hazır olan bir gövde bir yoklamada tamamlanır; içteki bir geleceği bekleyen dış bir gövde ise başkası bitince ilerler.

Önceki ders `HemenHazir` tipini kurdu: `poll` çağrıldığı anda biten, hiçbir zaman `Pending`
dönmeyen bir gelecek. Bu, mümkün en yalın gövdeydi — durmadan geçen bir gövde. Bu ders aynı
sözleşmeyi bir kez duran bir gövdeyle genişletiyor ve Rust'ın bu gövdeleri elle yazmak yerine
üretmesini sağlayan yazımı tanıtıyor: `async` ve `await`. Sorulan soru aynı kalıyor — bu iş
ilerlemek için neye ihtiyaç duyuyor — ama şimdi yanıt kaynakta görünür bir işaretle veriliyor.

Önceki dersteki `HemenHazir` ve bir sonraki bölümdeki `BirKezDuraklar`, `Future` trait'ini
elle gerçekleştiren tiplerdi: `poll` yöntemini kendi elleriyle yazdılar. Gerçek programlarda
bir eşzamansız gövdenin çoğu satırı elle yazılmıyor — `async` anahtar sözcüğüyle işaretlenmiş
sıradan bir fonksiyon gövdesi olarak yazılıyor ve derleyici, bu gövdeyi otomatik olarak bir
`Future` gerçekleştirimine çeviriyor. `async` ve `await`, elle yazılan `poll` mantığının
kaynak düzeyindeki kısaltması; bu ders o kısaltmanın altında hâlâ aynı sözleşmenin —
`Ready`/`Pending` ikilisinin — çalıştığını gösteriyor.

#### Duraklama Noktası Kaynakta Yazılıdır

Önceki dersteki `HemenHazir`, `Future` trait'ini elle gerçekleştiriyordu. `BirKezDuraklar`
aynı trait'i, bir kez `Pending` dönecek biçimde gerçekleştiriyor: ilk çağrıda durumunu
değiştirip kendini bir sonraki `poll` için uyandırıyor, ikinci çağrıda tamamlanıyor.

```rust
// ilerle.rs — cekirdegin kesiti: yurutucu, bir kez duraklayan gelecek, hemen hazir gelecek,
// ve icteki gelecegi bekleyen bir eszamansiz fonksiyon.

use std::future::Future;
use std::pin::Pin;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};

pub struct Uyandirici {
    pub hazir: Mutex<bool>,
    pub kosul: Condvar,
}

impl Wake for Uyandirici {
    fn wake(self: Arc<Self>) {
        *self.hazir.lock().unwrap() = true;
        self.kosul.notify_one();
    }
}

pub fn kostur<F: Future>(gorev: F) -> (F::Output, u32) {
    let mut gorev = Box::pin(gorev);
    let u = Arc::new(Uyandirici {
        hazir: Mutex::new(false),
        kosul: Condvar::new(),
    });
    let waker: Waker = u.clone().into();
    let mut baglam = Context::from_waker(&waker);
    let mut yoklama = 0;
    loop {
        yoklama += 1;
        match gorev.as_mut().poll(&mut baglam) {
            Poll::Ready(x) => return (x, yoklama),
            Poll::Pending => {
                let mut h = u.hazir.lock().unwrap();
                while !*h {
                    h = u.kosul.wait(h).unwrap();
                }
                *h = false;
            }
        }
    }
}

pub struct BirKezDuraklar {
    pub duraklandi: bool,
    pub ad: &'static str,
    pub kanal: Sender<String>,
}

impl Future for BirKezDuraklar {
    type Output = &'static str;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
        if self.duraklandi {
            self.kanal.send(self.ad.to_string()).unwrap();
            Poll::Ready(self.ad)
        } else {
            self.duraklandi = true;
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

pub struct HemenHazir {
    pub ad: &'static str,
    pub kanal: Sender<String>,
}

impl Future for HemenHazir {
    type Output = &'static str;
    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<&'static str> {
        self.kanal.send(self.ad.to_string()).unwrap();
        Poll::Ready(self.ad)
    }
}

pub async fn disariyi_bekle(ic: BirKezDuraklar, kanal: Sender<String>) -> &'static str {
    let _ = ic.await;
    kanal.send(String::from("gorev-await-eden")).unwrap();
    "await-eden"
}
```

`disariyi_bekle` bir `async fn`. Bu yazım, gövdesini elle bir `Future` gerçekleştirimine
çevirmekten kurtarıyor: derleyici, gövdeyi kendi kendine bir durum makinesine dönüştürüyor ve
bu makine `Future` trait'ini kendisi gerçekleştiriyor. Gövde içindeki `ic.await` satırı, bu
dönüşümün gözle görülür tek izi — **duraklama noktası**. Derleyici bu satırı gördüğünde,
durum makinesine "burada duraklayabilirim" diyen bir dal ekliyor: `ic`nin `poll`u `Pending`
dönerse, dış gövde de kendi `poll`unu `Pending` ile bitiriyor ve bir sonraki çağrıda kaldığı
yerden devam ediyor. `.await` bir makro çağrısı değil, bir işleç; kod bloğu dışında da adı
serbestçe geçebilir.

Üretilen durum makinesi kavram olarak, Rust Temelleri kursunda kurulan numaralandırmaya
yakın: sonlu sayıda ayrı durumdan biri her seferinde etkin oluyor, hangi durumun etkin olduğu
bir önceki `poll` çağrısının nerede kaldığına bağlı. Fark, bu numaralandırmanın elle
yazılmamış olması — derleyici, gövdedeki her `.await` sayısı kadar duraklama durumu üretiyor
ve aralarındaki geçişi kendisi kuruyor. `disariyi_bekle`de tek bir `.await` var; bu yüzden
derleyicinin ürettiği durum makinesi yalnız iki durum arasında geçiş yapıyor: `ic`yi
bekleyen durum ve tamamlanmış durum.

Fonksiyonun imzası — `async fn disariyi_bekle(...) -> &'static str` — kaynakta yazılan dönüş
tipi `&'static str` gibi görünse de, gerçek dönüş değeri derleyicinin ürettiği bu durum
makinesi: `Future<Output = &'static str>` sözleşmesini taşıyan, adı kaynakta hiç geçmeyen bir
tip. `kostur<F: Future>` imzasındaki `F::Output` ilişkili tipi tam olarak bunu karşılıyor —
`kostur`, kendisine hangi somut durum makinesinin verildiğini bilmeden, yalnız `Future`
sözleşmesine güvenerek çalışıyor. `disariyi_bekle(ic, gonder.clone())` çağrısının kendisi de
hiçbir şeyi çalıştırmıyor; önceki derste kurulan tembellik kuralı burada da geçerli — çağrı
yalnız durum makinesinin ilk durumunu kuruyor, `kostur`a verilene kadar hiçbir alanı
işlemiyor.

#### Duraklama Sayısı Koşuma Yansıyor

Aşağıdaki program üç gövdeyi aynı yürütücüyle koşturuyor: bir kez duraklayan `BirKezDuraklar`,
hiç duraklamayan `HemenHazir` ve içinde bir `BirKezDuraklar`i bekleyen `disariyi_bekle`.

```rust
// yoklama-sayilari.rs — bir kez duraklayan, hemen hazir olan ve icini bekleyen govdelerin
// yoklama sayilari.
mod ilerle;
use ilerle::{disariyi_bekle, kostur, BirKezDuraklar, HemenHazir};
use std::sync::mpsc::channel;

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

    let (deger_a, yoklama_a) = kostur(BirKezDuraklar {
        duraklandi: false,
        ad: "gorev-yoklanan-a",
        kanal: gonder.clone(),
    });
    let (deger_b, yoklama_b) = kostur(HemenHazir {
        ad: "gorev-hazir-b",
        kanal: gonder.clone(),
    });

    let ic = BirKezDuraklar {
        duraklandi: false,
        ad: "gorev-ic",
        kanal: gonder.clone(),
    };
    let (deger_c, yoklama_c) = kostur(disariyi_bekle(ic, gonder.clone()));

    drop(gonder);
    let mut kayitlar: Vec<String> = al.try_iter().collect();
    kayitlar.sort();

    println!("degerler: a={} b={} c={}", deger_a, deger_b, deger_c);
    println!("yoklama sayilari: a={} b={} c={}", yoklama_a, yoklama_b, yoklama_c);
    println!();
    println!("kayitlar ({}):", kayitlar.len());
    for k in &kayitlar {
        println!("  {}", k);
    }
}
```

```
degerler: a=gorev-yoklanan-a b=gorev-hazir-b c=await-eden
yoklama sayilari: a=2 b=1 c=2

kayitlar (4):
  gorev-await-eden
  gorev-hazir-b
  gorev-ic
  gorev-yoklanan-a
```

**AS2.** `gorev-yoklanan-a` iki yoklamada bitti: ilk çağrı `duraklandi`yi `true` yapıp
`Pending` döndü, ikinci çağrı `kanal.send` çağırıp `Ready` döndü. `gorev-hazir-b` tek
yoklamada bitti çünkü hiç duraklamadı. Bu iki sayı, aynı yürütücüyle koşturulan iki farklı
gövdenin **kaç kez** yoklandığının kaynaktaki davranışlarından doğrudan çıkarılabilir olduğunu
gösteriyor — yoklama sayısı burada ortama bağlı bir yan bilgi değil, gövdenin kendi
mantığından gelen belirlenimci bir sonuç. Beş ayrı koşumda üç sayı da birebir aynı kaldı.

`disariyi_bekle`nin yoklama sayısı da ikiydi, ama nedeni farklı. Bu gövde `ic.await`e
ulaştığında, kendi `poll`u içeriden `ic`nin `poll`unu çağırıyor. İlk dış çağrıda `ic`
`Pending` döndürüyor, dış gövde de bunu olduğu gibi yukarı taşıyıp `Pending` dönüyor. İkinci
dış çağrıda `ic` tamamlanıyor (`gorev-ic` kaydı burada yazılıyor), `.await` satırı bitiyor,
kalan gövde (`gorev-await-eden` kaydı) aynı çağrı içinde çalışıp `Ready` dönüyor. İki yoklama
da, ama biri kendi durumundan, öbürü içindeki başka bir geleceğin durumundan geliyor.

#### Duraklama Noktası Sayısı Yoklama Sayısını Belirler

Bir gövde tek bir `.await` ile sınırlı değil; art arda birden fazla duraklama noktası
taşıyabilir. Aşağıdaki kesit `disariyi_bekle`nin yerine, iki ayrı `BirKezDuraklar`ı sırayla
bekleyen bir gövde tanımlıyor. `HemenHazir` ve `disariyi_bekle` bu ölçümde gerekmediği için
kesit dışında bırakılıyor.

```rust
// ilerle.rs — cekirdegin kesiti degisti: iki ardisik duraklama noktasi tasiyan bir govde
// ekleniyor, HemenHazir ve disariyi_bekle bu olcumde gerekmiyor.

use std::future::Future;
use std::pin::Pin;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};

pub struct Uyandirici {
    pub hazir: Mutex<bool>,
    pub kosul: Condvar,
}

impl Wake for Uyandirici {
    fn wake(self: Arc<Self>) {
        *self.hazir.lock().unwrap() = true;
        self.kosul.notify_one();
    }
}

pub fn kostur<F: Future>(gorev: F) -> (F::Output, u32) {
    let mut gorev = Box::pin(gorev);
    let u = Arc::new(Uyandirici {
        hazir: Mutex::new(false),
        kosul: Condvar::new(),
    });
    let waker: Waker = u.clone().into();
    let mut baglam = Context::from_waker(&waker);
    let mut yoklama = 0;
    loop {
        yoklama += 1;
        match gorev.as_mut().poll(&mut baglam) {
            Poll::Ready(x) => return (x, yoklama),
            Poll::Pending => {
                let mut h = u.hazir.lock().unwrap();
                while !*h {
                    h = u.kosul.wait(h).unwrap();
                }
                *h = false;
            }
        }
    }
}

pub struct BirKezDuraklar {
    pub duraklandi: bool,
    pub ad: &'static str,
    pub kanal: Sender<String>,
}

impl Future for BirKezDuraklar {
    type Output = &'static str;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
        if self.duraklandi {
            self.kanal.send(self.ad.to_string()).unwrap();
            Poll::Ready(self.ad)
        } else {
            self.duraklandi = true;
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

pub async fn iki_kez_bekle(ic1: BirKezDuraklar, ic2: BirKezDuraklar) -> &'static str {
    let _ = ic1.await;
    let _ = ic2.await;
    "iki-kez-bekleyen"
}
```

```rust
// iki-duraklama.rs — iki ardisik duraklama noktasi tasiyan bir govdenin yoklama sayisi.
mod ilerle;
use ilerle::{iki_kez_bekle, kostur, BirKezDuraklar};
use std::sync::mpsc::channel;

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

    let ic1 = BirKezDuraklar {
        duraklandi: false,
        ad: "gorev-ic-birinci",
        kanal: gonder.clone(),
    };
    let ic2 = BirKezDuraklar {
        duraklandi: false,
        ad: "gorev-ic-ikinci",
        kanal: gonder.clone(),
    };
    let (deger, yoklama) = kostur(iki_kez_bekle(ic1, ic2));

    drop(gonder);
    let mut kayitlar: Vec<String> = al.try_iter().collect();
    kayitlar.sort();

    println!("deger: {}", deger);
    println!("yoklama sayisi: {}", yoklama);
    println!("kayitlar ({}):", kayitlar.len());
    for k in &kayitlar {
        println!("  {}", k);
    }
}
```

```
deger: iki-kez-bekleyen
yoklama sayisi: 3
kayitlar (2):
  gorev-ic-birinci
  gorev-ic-ikinci
```

**AS3.** İki duraklama noktası üç yoklamaya karşılık geliyor: ilk çağrı `ic1`i `Pending`
yapıyor, ikinci çağrı `ic1`i bitirip `ic2`nin ilk `poll`unu tetikliyor (o da `Pending`
dönüyor, aynı dış çağrı içinde), üçüncü çağrı `ic2`yi bitirip gövdenin kalanını çalıştırıyor.
Genel kural buradan çıkıyor: art arda gelen $n$ duraklama noktası, art arda gelen $n+1$
yoklama gerektiriyor — son çağrı her zaman ilerlemeyi tamamlayan, `Pending` döndürmeyen
çağrı. Bu sayı da beş ayrı koşumda birebir aynı kaldı; kaynaktaki `.await` sayısı, koşumdaki
yoklama sayısını önceden belirliyor. `gorev-ic-ikinci` kaydı hiçbir koşumda
`gorev-ic-birinci` kaydı oluşmadan önce yazılamaz — ikinci duraklama noktası, `ic2`nin ilk
`poll`u ancak `ic1` tamamlandıktan sonra çağrıldığı için, birincisi bitmeden hiç
değerlendirilmiyor. Bu sıra, defterde alfabetik olarak sıralandığı için değil, durum
makinesinin dallarını yalnız bir öncekini tamamladıktan sonra geçebildiği için garanti
altında.

#### Beklemek ile Yoklanmak Aynı Şey Değil

`gorev-ic` — `disariyi_bekle`nin içindeki `BirKezDuraklar` örneği — **yoklanınca** sınıfına
giriyor: `gorev-yoklanan-a` ile aynı tip, aynı davranış, `.await` içinde kullanılmış olması
sınıfını değiştirmiyor. Buna karşılık `gorev-await-eden` kaydı — `disariyi_bekle`nin kendi
tamamlanma satırı — **başkası bitince** sınıfına giriyor. Fark şu: `gorev-ic` doğrudan
yoklanıyor ve kendi ilerlemesi için yalnız tekrar tekrar çağrılmaya ihtiyaç duyuyor;
`disariyi_bekle` ise `ic` tamamlanmadan hiçbir zaman kendi son satırına ulaşamıyor —
ilerlemesi kendi yoklanmasına değil, **başka bir görevin bitmesine** bağlı.

Go'nun Eşzamanlılık kursu bir birimin nasıl duracağını ölçmüştü — kendi, beklediği geldiği
için, iptal edildiği için ya da hiç durmadan. Bu ders aynı soruyu sormuyor; burada ölçülen
bitiş değil, **duraklayıp nereden devam ettiği**. `disariyi_bekle` hiçbir zaman durmuyor,
yalnız bir kez `Pending` dönüp aynı noktadan devam ediyor — ve bu devam noktası, `.await`
yazıldığı satırla birebir eşleşiyor. Kaynaktaki tek bir satır, koşumdaki duraklama noktasını
önceden gösteriyor.

Bu ayrım, İş Parçacıkları konusundaki kanal bekleyişiyle karşılaştırıldığında daha net
görünüyor. O konuda bir iş parçacığı bir kanaldan haber beklerken işletim sistemi tarafından
askıya alınıyordu; iş parçacığının kendisi, beklediği süre boyunca hiçbir işlemci zamanı
tüketmiyordu ama bu askıya alma programcının kontrolünün dışındaydı. `disariyi_bekle` ise
kendi askıya alınmasını **kendi yazdığı** `.await` satırıyla tarif ediyor; yürütücü bu satırı
görmüyor bile, yalnız `poll`un döndürdüğü `Pending`i görüyor. Askıya alınma kararı burada
kaynağın kendisinde, iş parçacığı örneğinde ise çalışma zamanının bir kararında duruyor.

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

Duraklama noktası kaynakta yazılı, ama **ne kadar duraklayacağı yazılı değil**. `ic.await`
satırı, `disariyi_bekle`nin orada durabileceğini söylüyor; kaç `poll` çağrısı sonra devam
edeceği, `ic`nin kendi mantığına bağlı ve bu ders yalnız **sayıyı** basıyor, süreyi değil.
`BirKezDuraklar` bir kez duruyor çünkü öyle yazıldı; başka bir gerçekleştirim on kez durabilir,
kaynaktaki `.await` görünümü değişmez. Duraklama noktasının görünürlüğü, duraklamanın **var
olduğunu** kaynaktan okumayı sağlıyor; kaç kez tekrarlanacağını değil.

Bu, kursun ortama bağlı veri yasağının bir sonraki dersteki I/O örneğinde de karşımıza
çıkacak biçimi: modellenen bir bekleme, gerçek bir gecikmenin süresini değil, yalnız kaç kez
yoklandığını ölçülebilir kılıyor.

Aynı sınır, iki ardışık duraklama noktasının ölçümünde de geçerli: `n+1` kuralı yalnız
duraklama noktası **sayısını** sayıyor, her duraklamanın ne kadar sürdüğünü değil. `ic1` ile
`ic2` arasında gerçek bir programda saniyeler geçebilir ya da hiç geçmeyebilir — kural
değişmez, çünkü kural yoklama çağrılarının **kaç kez** yapıldığını, aralarında ne kadar
zaman geçtiğini değil.

#### Özet

- `async fn`, bir gövdeyi elle `Future` gerçekleştirmek yerine derleyicinin otomatik ürettiği
  bir durum makinesine çevirir; `.await` bu makinedeki **duraklama noktasını** işaretler.
- Bir kez duraklayan gövde iki yoklamada, hiç duraklamayan gövde bir yoklamada tamamlanır;
  yoklama sayısı belirlenimcidir ve beş ayrı koşumda değişmez.
- İçteki bir geleceği bekleyen gövde de **yoklanınca** sınıfına girer, ama o gövdenin kendi
  tamamlanma noktası ilerlemek için **başkası bitince** sınıfına girer — ikisi aynı görev
  içinde bir arada bulunabilir.
- Go'nun Eşzamanlılık kursu bir birimin nasıl duracağını (bitiş) ölçtü; bu ders nerede
  duraklayıp nasıl devam ettiğini (duraklama ve devam) ölçüyor.
- Duraklama noktası kaynakta görünür yazılır, ama kaç kez tetikleneceği kaynaktan okunmaz —
  yalnız koşumda ölçülür. Art arda gelen $n$ duraklama noktası, art arda gelen $n+1$ yoklama
  gerektirir: son çağrı her zaman `Pending` döndürmeyen, ilerlemeyi tamamlayan çağrıdır.
- Bir `async fn`in gövdesi çağrıldığı anda çalışmaz, yalnız derleyicinin ürettiği durum
  makinesinin ilk durumunu kurar — önceki dersteki tembellik kuralı burada da geçerlidir.

#### Sonraki Adım

Bu ders, bir gövdenin kaç kez duraklayıp devam ettiğini ölçtü; henüz sorulmayan soru, bu
duraklama ve devam çağrılarının **kim tarafından** zamanlandığı. `kostur` şimdiye kadar
verili bir araç olarak kullanıldı — kod her defasında aynı işlevi çağırdı ve onun uyandırılana
kadar beklediğini varsaydı. Sıradaki ders bu varsayımı sorguluyor ve `kostur`u doğrudan konu
ediniyor: aynı gövdeyi iki farklı yürütücü kurgusuyla koşturup, yürütücünün görevin gövdesine
hiçbir şey eklemediğini, yalnız **ne zaman** yoklayacağına karar verdiğini ölçüyor.

### Yürütücüler

Source: https://academia.sh/tr/kurslar/rust-eszamanlilik/yurutuculer

Rust'ın standart kitaplığında yürütücü yoktur; kursun kendi elle yazdığı iki farklı yürütücü kurgusu aynı görevi koşturduğunda birebir aynı yoklama sayısını verir — yürütücü görevin gövdesine hiçbir şey eklemez, yalnız ne zaman yoklayacağına karar verir.

Önceki iki ders `kostur`u verili bir araç olarak kullandı: bir gelecek verildi, sonucu ve
yoklama sayısı geri geldi. Aradaki mekanizma hiç sorgulanmadı. Bu ders tam olarak o
mekanizmayı açıyor. Rust'ın standart kitaplığında bir yürütücü yoktur — `Future`, `Pin`,
`Context`, `Poll` ve `Waker` birer sözleşme tanımlar, ama bu sözleşmeyi kimin, ne zaman
çağıracağına dair hiçbir karar vermez. Bu boşluğu dolduran kod, kursun ölçüm çekirdeğinde elle
yazılmış durumda ve bu ders onu doğrudan konu ediniyor.

Bu boşluk, dilin bir eksikliği değil, bilinçli bir sınır. Bir yürütücünün doğru kararı, hangi
programın çalıştığına göre değişir: az sayıda uzun süren görevi olan bir program başka bir
zamanlama isterken, binlerce kısa görevi olan bir program başka bir zamanlama ister. Dil, tek
bir politikayı dayatmak yerine sözleşmeyi (`Future`, `Poll`, `Waker`) standartlaştırıp
zamanlamayı ayrı bir bileşene bırakıyor. Bu ders iki farklı yürütücü kurup aynı görevleri her
ikisiyle de koşturarak bu ayrımın kaynakta ne anlama geldiğini gösteriyor: yürütücüyü
değiştirmek, görevi tanımlayan kodun tek bir satırını bile değiştirmiyor.

#### Yürütücü Sözleşmenin Dışında Kalan Parçadır

`Future` trait'i yalnız `poll` yöntemini bildirir; bu yöntemi kimin, hangi sırayla, ne kadar
sıklıkla çağıracağını hiçbir yerde söylemez. Bu boşluk kasıtlı: dilin kendisi tek bir
zamanlama politikasını dayatmıyor, zamanlamayı ayrı bir bileşene — yürütücüye — bırakıyor.
Aşağıdaki kesit iki farklı yürütücü tanımlıyor. Birincisi önceki derslerde kullanılan `kostur`
— uyandırılana kadar bir koşul değişkeninde bekleyen kurgu. İkincisi `kostur_dongu` — hiç
beklemeyen, `Pending` döner dönmez hemen yeniden yoklayan bir kurgu.

```rust
// ilerle.rs — cekirdegin kesiti: iki farkli yurutucu kurgusu, ayni gorev turleri.

use std::future::Future;
use std::pin::Pin;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};

pub struct Uyandirici {
    pub hazir: Mutex<bool>,
    pub kosul: Condvar,
}

impl Wake for Uyandirici {
    fn wake(self: Arc<Self>) {
        *self.hazir.lock().unwrap() = true;
        self.kosul.notify_one();
    }
}

// yurutucu 1: uyandirilana kadar bekler
pub fn kostur<F: Future>(gorev: F) -> (F::Output, u32) {
    let mut gorev = Box::pin(gorev);
    let u = Arc::new(Uyandirici {
        hazir: Mutex::new(false),
        kosul: Condvar::new(),
    });
    let waker: Waker = u.clone().into();
    let mut baglam = Context::from_waker(&waker);
    let mut yoklama = 0;
    loop {
        yoklama += 1;
        match gorev.as_mut().poll(&mut baglam) {
            Poll::Ready(x) => return (x, yoklama),
            Poll::Pending => {
                let mut h = u.hazir.lock().unwrap();
                while !*h {
                    h = u.kosul.wait(h).unwrap();
                }
                *h = false;
            }
        }
    }
}

pub struct BosUyandirici;

impl Wake for BosUyandirici {
    fn wake(self: Arc<Self>) {}
}

// yurutucu 2: uyanma sinyalini beklemez, hemen yeniden yoklar
pub fn kostur_dongu<F: Future>(gorev: F) -> (F::Output, u32) {
    let mut gorev = Box::pin(gorev);
    let u = Arc::new(BosUyandirici);
    let waker: Waker = u.clone().into();
    let mut baglam = Context::from_waker(&waker);
    let mut yoklama = 0;
    loop {
        yoklama += 1;
        if let Poll::Ready(x) = gorev.as_mut().poll(&mut baglam) {
            return (x, yoklama);
        }
    }
}

pub struct BirKezDuraklar {
    pub duraklandi: bool,
    pub ad: &'static str,
    pub kanal: Sender<String>,
}

impl Future for BirKezDuraklar {
    type Output = &'static str;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
        if self.duraklandi {
            self.kanal.send(self.ad.to_string()).unwrap();
            Poll::Ready(self.ad)
        } else {
            self.duraklandi = true;
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

pub struct HemenHazir {
    pub ad: &'static str,
    pub kanal: Sender<String>,
}

impl Future for HemenHazir {
    type Output = &'static str;
    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<&'static str> {
        self.kanal.send(self.ad.to_string()).unwrap();
        Poll::Ready(self.ad)
    }
}
```

İki yürütücü de aynı üç adımı izliyor: yoklama sayacını artır, `poll`u çağır, sonucu
değerlendir. Ayrıldıkları tek yer `Pending` dönüşünde ne yaptıkları. `kostur`, `Uyandirici`nin
taşıdığı koşul değişkeninde gerçekten bekliyor ve yalnız `wake` çağrıldığında devam ediyor.
`kostur_dongu` ise `BosUyandirici` adlı, `wake` çağrısında hiçbir şey yapmayan bir tip
kullanıyor ve `Pending` her döndüğünde beklemeden yeniden `poll` çağırıyor. İkisi de aynı
`Future` sözleşmesine uyuyor; ikisi de geçerli birer yürütücü. Aralarındaki fark, görevin
kendisinde değil, görevi **ne zaman** tekrar yoklayacaklarında.

İki `Wake` gerçekleştirimi arasındaki fark da bu tercihi yansıtıyor. `Uyandirici`, bir
mantıksal bayrak ve bir koşul değişkeni taşıyarak `wake` çağrısını bekleyen tarafa gerçekten
iletiyor — kim çağırırsa çağırsın, `kostur` bu çağrıyı görecek ve uyanacak. `BosUyandirici`
ise `wake`i hiç kullanmıyor; gövdesi boş, çünkü `kostur_dongu` zaten `wake` çağrısını
beklemeden yeniden yokluyor. `Waker`in kendisi hâlâ her iki gelecek türüne veriliyor —
`BirKezDuraklar`ın `poll`u kendi `Context`ine hangi yürütücünün taşıdığı hangi `Wake`
gerçekleştirimini içerdiğini bilmeden, koşulsuz olarak `wake_by_ref` çağırıyor. Sözleşmeye
uymak bu kadarını istiyor; ötesi yürütücünün kendi kararı.

Her iki yürütücü de kendi uyandırıcısını bir `Arc` içinde tutuyor. Sahiplik ve Bellek Modeli
kursunun ölçtüğü paylaşımlı sahiplik burada tam olarak kullanım yerini buluyor: `Waker`,
`Context::from_waker` içinden geleceğin `poll` yöntemine bir referans olarak geçiyor, ama
uyandırıcının kendisi yürütücünün döngüsünde de yaşamaya devam etmesi gerekiyor. `Arc`,
uyandırıcının hem `Waker`in içinde hem yürütücünün kendi değişkeninde aynı anda var
olabilmesini sağlıyor — hangisi önce bırakılırsa bırakılsın, öbürü elindeki kopyayı
kullanmaya devam edebiliyor. `BosUyandirici` da aynı düzeni izliyor, tuttuğu veri boş olsa
bile.

#### Aynı Görev, İki Yürütücü, Aynı Yoklama Sayısı

Aşağıdaki program hem bir kez duraklayan hem de hemen hazır olan görevleri, aynı tanımdan
kurup her ikisini de iki ayrı yürütücüyle koşturuyor.

```rust
// iki-yurutucu.rs — ayni gorev turu, iki farkli yurutucu, ayni yoklama sayisi.
mod ilerle;
use ilerle::{kostur, kostur_dongu, BirKezDuraklar, HemenHazir};
use std::sync::mpsc::channel;

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

    let (deger_a1, yoklama_a1) = kostur(BirKezDuraklar {
        duraklandi: false,
        ad: "duraklar-kostur",
        kanal: gonder.clone(),
    });
    let (deger_a2, yoklama_a2) = kostur_dongu(BirKezDuraklar {
        duraklandi: false,
        ad: "duraklar-dongu",
        kanal: gonder.clone(),
    });

    let (deger_b1, yoklama_b1) = kostur(HemenHazir {
        ad: "hazir-kostur",
        kanal: gonder.clone(),
    });
    let (deger_b2, yoklama_b2) = kostur_dongu(HemenHazir {
        ad: "hazir-dongu",
        kanal: gonder.clone(),
    });

    drop(gonder);
    let mut kayitlar: Vec<String> = al.try_iter().collect();
    kayitlar.sort();

    println!(
        "bir kez duraklayan: kostur={} kostur_dongu={} (deger {} / {})",
        yoklama_a1, yoklama_a2, deger_a1, deger_a2
    );
    println!(
        "hemen hazir:        kostur={} kostur_dongu={} (deger {} / {})",
        yoklama_b1, yoklama_b2, deger_b1, deger_b2
    );
    println!(
        "ayni yoklama sayisi mi: duraklayan={} hazir={}",
        yoklama_a1 == yoklama_a2,
        yoklama_b1 == yoklama_b2
    );
    println!();
    println!("kayitlar ({}):", kayitlar.len());
    for k in &kayitlar {
        println!("  {}", k);
    }
}
```

```
bir kez duraklayan: kostur=2 kostur_dongu=2 (deger duraklar-kostur / duraklar-dongu)
hemen hazir:        kostur=1 kostur_dongu=1 (deger hazir-kostur / hazir-dongu)
ayni yoklama sayisi mi: duraklayan=true hazir=true

kayitlar (4):
  duraklar-dongu
  duraklar-kostur
  hazir-dongu
  hazir-kostur
```

**AS4.** Dört ayrı çağrı, iki farklı yürütücü üzerinden iki farklı görev türünü koşturuyor ve
sonuç aynı kalıyor: bir kez duraklayan görev her iki yürütücüde de iki yoklamada, hemen hazır
olan görev her iki yürütücüde de bir yoklamada bitiyor. Bu beş ayrı koşumda birebir aynı kaldı.
`BirKezDuraklar`ın gövdesi hiç değişmedi — hangi yürütücünün onu çağırdığını bilmiyor bile,
`Context` içindeki `Waker`i çağırmaktan başka bir şey yapmıyor. Yoklama sayısını belirleyen,
görevin kendi `poll` mantığı; yürütücü yalnız bu mantığı **ne sıklıkla** tetikleyeceğine karar
veriyor.

#### Kaç Görev Aynı Anda Koşuyor

`kostur` ve `kostur_dongu` her çağrıda tek bir kök gelecek alıyor ve o gelecek bitene kadar
çağıran iş parçacığını bırakmıyor. Bu, gerçek bir yürütücünün genellikle yaptığı işin
yalınlaştırılmış hâli: bir yürütücü çoğunlukla aynı anda birden fazla göreve bakan bir kuyruk
tutar, hangisinin yoklanacağına o kuyruktan seçerek karar verir. Bu dersteki dört çağrı — iki
yürütücünün her biriyle koşturulan `BirKezDuraklar` ve `HemenHazir` örnekleri — birbirinden
bağımsız, art arda çalışıyor; hiçbiri bir ötekiyle aynı anda yoklanmıyor. Yürütücünün
**birden fazla** görevi aynı anda gözetmesi
ayrı bir tasarım sorusu ve bu dersin ölçümünün dışında kalıyor — burada sabitlenen, tek bir
görevi ilerletmenin en yalın biçimi. İki yürütücü kurgusunun aynı sayıyı vermesi, bu yalın
biçimde bile yürütücünün seçiminin gövdeye hiçbir şey eklemediğini göstermeye yetiyor. Bir
yürütücü birden fazla görevi aynı anda gözetmeye başladığında ortaya çıkan yeni soru —
hangisinin önce yoklanacağı, birinin ötekini ne kadar bekleteceği — bu dersin ölçtüğü
tek-görevli sayıyı değiştirmiyor; yalnız görevler arasındaki **sırayı** ilgilendiren, bu
dersin dışında kalan ayrı bir soru.

#### Yürütücü Ne Yaptığını Değil, Ne Zaman Yoklayacağını Belirler

`kostur` ile `kostur_dongu` arasındaki fark, koşumun **gözlemlenebilir** kısmında hiç
görünmüyor: aynı değerler, aynı yoklama sayıları, aynı kayıt kümesi. Fark yalnız `Pending`
dönüşü ile bir sonraki `poll` çağrısı arasında geçen — ve bu kursun basmayı yasakladığı —
sürede yaşıyor. `kostur`, çağıran iş parçacığını bu süre boyunca gerçekten uyutuyor;
`kostur_dongu` aynı iş parçacığını meşgul bir döngüde tutuyor. İkisi de doğru sonucu üretiyor,
ama biri işlemciyi bekleme sırasında serbest bırakıyor, öbürü bırakmıyor. Bu, iş parçacıklarını
ele alan konuda ölçülen bir sorunun — bir iş parçacığının beklerken ne yaptığının — burada
yürütücü düzeyinde tekrar karşımıza çıkan biçimi; ama bu ders bunu **süre** üzerinden değil,
**yoklama sayısının değişip değişmediği** üzerinden ölçüyor ve değişmediğini gösteriyor.

Trait'ler, Genellikler ve Hata Yönetimi kursu bir gövdenin kaç kez koştuğunu ölçmüştü — her
seferinde, yalnız gerektiğinde ya da hiç. Bu ders farklı bir soru soruyor: aynı sayıda koşan
bir gövdeyi **kim** ilerletiyor. Yürütücü, tam olarak bu sorunun yanıtı — bir gövdenin kaç kez
koştuğuna karışmayan, yalnız o koşuşların ne zaman gerçekleşeceğine karar veren özne. `kostur`
ile `kostur_dongu` bu özne rolünü iki farklı biçimde oynuyor, ama ikisi de aynı sayıda
tetikliyor.

Bu iki kurgu arasındaki seçim, gerçek bir programda önemsiz değil. `kostur_dongu`, meşgul bir
döngüyle işlemciyi sürekli meşgul tutuyor; bir görev `Pending` döndürdüğü anda hazır olmasa
bile döngü onu tekrar tekrar sorguluyor. `kostur` ise işlemciyi koşul değişkeni beklerken
serbest bırakıyor ve yalnız `wake` çağrıldığında geri dönüyor. İkisi de bu derste ölçülen
yoklama sayısını değiştirmiyor, çünkü `BirKezDuraklar`ın `Pending` dönme kararı zaten
`duraklandi` alanına bakılarak veriliyor — dışarıdan ne kadar sık sorulduğuna bakmadan aynı
kararı veriyor. Gerçek bir kaynağı bekleyen bir gelecekte (bir ağdan cevap, bir dosyadan
veri) bu fark daha da belirginleşir, ama bu dersin ölçtüğü sayı üzerinde hâlâ hiçbir etkisi
olmaz — sıradaki ders tam olarak böyle bir kaynağı modelliyor.

#### Aynı Yürütücü, İki Farklı Gelecek Türü

`kostur` ve `kostur_dongu` her ikisi de `F: Future` sınırıyla genel yazıldı; bu yüzden aynı
işlev hem `BirKezDuraklar` hem `HemenHazir` için, tek bir gövde değişikliği yapmadan
çağrılabiliyor. Trait'ler, Genellikler ve Hata Yönetimi kursu genel bir gövdenin her somut tip
için derleyici tarafından ayrı üretildiğini ölçmüştü; burada da aynı gerçekleşiyor —
`kostur::<BirKezDuraklar>` ile `kostur::<HemenHazir>` derleyicinin ürettiği iki ayrı gövde. Bu
ders o eksene dönmüyor, ama sonucu ondan yararlanıyor: yürütücü kodu tek bir yerde yazılıyor,
her yeni gelecek türü için yeniden yazılmasına gerek kalmıyor. `iki-yurutucu.rs`deki dört
çağrının hepsi aynı iki işlevi (`kostur`, `kostur_dongu`) çağırıyor; hiçbiri yürütücünün
gövdesini kopyalamıyor.

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

Yürütücüyü değiştirmek görevin gövdesini değiştirmiyor. `BirKezDuraklar`ın `poll` yöntemi
hangi yürütücünün onu çağırdığına bakmıyor bile — `duraklandi` alanının değerine bakıyor ve
kendi kararını veriyor. Aynı görev iki kurguda aynı kaydı üretiyor; fark yalnız yoklama
düzeninde kalıyor, üretilen değerde ya da kayıtta değil. Bu sınır, bir yürütücünün neyi
**değiştiremeyeceğini** gösteriyor: bir görevin ne ürettiğini, kaç adımda ürettiğini ya da
hangi sırayla dallandığını. Yürütücü yalnız `poll` çağrısının **zamanlamasını** değiştirebilir;
bunun ötesinde bir görev üzerinde hiçbir yetkisi yoktur.

Bu sınır, birden fazla görevin aynı yürütücüde bir arada bulunduğu durumlarda daha görünür
hale gelir: yürütücü, hangi görevin **önce** yoklanacağına karar verebilir, ama bir görevin
kendi `poll`u içinde kaç kez `Pending` döneceğine karışamaz. Bu dersteki tek görevlik
örneklerde bu ayrım gizli kalıyor çünkü sırayı belirleyecek başka bir görev yok; ama kural
zaten burada da geçerli — `kostur` ile `kostur_dongu` birbirinden çok farklı zamanlama
stratejileri izlese de, `BirKezDuraklar`ın iki yoklamada bitmesi kararını hiçbiri değiştiremedi.

#### Özet

- Rust'ın standart kitaplığında yürütücü yoktur; `poll`u çağıracak döngü elle yazılır.
- Bir yürütücü, `Pending` dönüşünde ne yapacağına karar veren bileşendir — beklemek,
  hemen yeniden yoklamak ya da başka bir strateji izlemek yürütücünün seçimidir.
- Aynı görev türü iki farklı yürütücüyle koşturulduğunda aynı yoklama sayısını verir: yürütücü
  görevin gövdesine hiçbir şey eklemez.
- Yürütücü, bir gövdeyi ilerleten **özne**dir; Trait'ler, Genellikler ve Hata Yönetimi
  kursunun ölçtüğü "kaç kez koştu" sorusundan farklı olarak, bu ders "kim ilerletti" sorusunu
  sorar.
- Bu derste koşturulan dört görev de **yoklanınca** sınıfına girer: hiçbiri kendi başına
  ilerlemiyor, dördü de bir yürütücünün `poll` çağrısına ihtiyaç duyuyor — değişen yalnız
  hangi yürütücünün bu çağrıyı yaptığı, sınıfın kendisi değil.
- İki yürütücü arasındaki gerçek fark koşum sayısında değil, `Pending` dönüşü ile yeniden
  yoklama arasında geçen — ve basılmayan — süredeki davranışta yaşar; `kostur` ve
  `kostur_dongu` genel (`F: Future`) yazıldığı için aynı yürütücü gövdesi her gelecek türüyle
  çalışır.

#### Sonraki Adım

Bu ders, bir yürütücünün görevin **ne ürettiğine** karışmadığını, yalnız **ne zaman**
yoklandığına karar verdiğini gösterdi; iki bambaşka zamanlama stratejisi, aynı görev
üzerinde birebir aynı yoklama sayısını verdi. Buraya kadar her görev ya hemen hazırdı ya da
bir kez duraklayıp kendi kendine devam etti — ikisinde de duraklamanın nedeni görevin kendi
mantığıydı, dışarıdan bir koşula bağlı değildi. Sıradaki ders, dışarıdan bir kaynağın — girdi
ya da çıktının — henüz hazır olmadığı durumu ele alıyor: hazır olmayan bir kaynak `Pending`
döner, hazır olduğunda yürütücüyü uyandırır. Ölçülen yine yoklama sayısı, ama bu kez o sayı
kaynağın hazır olma süresine bağlı olarak değişiyor ve bu ders kurulan iki yürütücüden birini
— `kostur`u — yeniden kullanıyor.

### Eşzamansız Girdi/Çıktı

Source: https://academia.sh/tr/kurslar/rust-eszamanlilik/eszamansiz-girdi-cikti

Girdi çıktı beklerken duraklama, gerçek bir ağ ya da dosya değil, hazır olmayınca Pending dönen bir kaynakla modellenir; ölçülen kaç yoklamada tamamlandığı, kaynağın hazır olma süresi değil.

Önceki ders iki görev türünü inceledi: hiç duraklamayan ve kendi kararıyla bir kez duraklayan.
İkisinde de duraklamanın nedeni görevin kendi mantığıydı — dışarıdan hiçbir koşula bağlı
değildi. Bir programın gerçek dünyada beklediği şeylerin çoğu böyle değil: bir ağ bağlantısı
cevap vermeden önce belirsiz bir süre geçer, bir dosya sistemine gönderilen okuma isteği
donanımın hazır olmasını bekler. Bu ders, bu tür bir bekleyişi ele alıyor — ama kursun ortama
bağlı veri yasağı gereği gerçek bir ağ ya da dosya kullanmadan. Bekleme, hazır olmayan bir
kaynağın `Pending` döndüğü, hazır olduğunda kendini uyandırdığı bir modelle kuruluyor.

Gerçek bir ağ soketi ya da dosya tanıtıcısı kullanmamanın nedeni yalnız kolaylık değil.
Kursun başından beri sürdürdüğü belirlenimcilik kısıtı, koşumun hangi makinede çalıştırılırsa
çalıştırılsın aynı sayıyı vermesini istiyor. Gerçek bir ağ isteğinin ne kadar süreceği ağın
kendisine, gerçek bir dosya okumasının ne kadar süreceği depolama aygıtına bağlı — bu
kursun beş ayrı koşumda birebir aynı çıktı vermesini imkânsız kılardı. Bunun yerine,
gerçek dünyadaki "kaynak henüz hazır değil" durumunun **soyut biçimi** yakalanıyor: bir
`poll` çağrısının `Pending` dönmesi ve daha sonra bir `Ready`ye dönüşmesi. Kaynağın neden
hazır olmadığı — bir ağ gecikmesi mi, bir disk erişimi mi — bu modelin dışında kalıyor;
modelin içinde kalan tek şey, hazır olana kadar kaç kez sorulacağı.

#### Hazır Olmayan Bir Kaynağı Modellemek

`SahteKaynak`, bir girdi/çıktı kaynağının en indirgenmiş hâli: kaç yoklama sonra hazır
olacağını bir sayaçla taşıyor. Her `poll` çağrısında sayaç sıfır değilse bir azalıyor,
`Pending` dönüyor ve kendini uyandırıyor; sıfıra ulaştığında kanala bir kayıt yazıp `Ready`
dönüyor.

```rust
// ilerle.rs — cekirdegin kesiti: hazir olmayan bir kaynagi modelleyen gelecek.

use std::future::Future;
use std::pin::Pin;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};

pub struct Uyandirici {
    pub hazir: Mutex<bool>,
    pub kosul: Condvar,
}

impl Wake for Uyandirici {
    fn wake(self: Arc<Self>) {
        *self.hazir.lock().unwrap() = true;
        self.kosul.notify_one();
    }
}

pub fn kostur<F: Future>(gorev: F) -> (F::Output, u32) {
    let mut gorev = Box::pin(gorev);
    let u = Arc::new(Uyandirici {
        hazir: Mutex::new(false),
        kosul: Condvar::new(),
    });
    let waker: Waker = u.clone().into();
    let mut baglam = Context::from_waker(&waker);
    let mut yoklama = 0;
    loop {
        yoklama += 1;
        match gorev.as_mut().poll(&mut baglam) {
            Poll::Ready(x) => return (x, yoklama),
            Poll::Pending => {
                let mut h = u.hazir.lock().unwrap();
                while !*h {
                    h = u.kosul.wait(h).unwrap();
                }
                *h = false;
            }
        }
    }
}

// hazir olmayan bir girdi/cikti kaynagini modelliyor: her yoklamada bir yoklama kalan
// sayisini azaltiyor, kalan sifira dusunce hazir kabul edip degeri gonderiyor.
pub struct SahteKaynak {
    pub kalan: u32,
    pub ad: &'static str,
    pub kanal: Sender<String>,
}

impl Future for SahteKaynak {
    type Output = &'static str;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
        if self.kalan == 0 {
            self.kanal.send(self.ad.to_string()).unwrap();
            Poll::Ready(self.ad)
        } else {
            self.kalan -= 1;
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

pub async fn veriyi_oku(kaynak: SahteKaynak, kanal: Sender<String>) -> &'static str {
    let _ = kaynak.await;
    kanal.send(String::from("okuma-tamamlandi")).unwrap();
    "okundu"
}
```

`SahteKaynak`, önceki dersteki `BirKezDuraklar`ın genellenmiş biçimi: `BirKezDuraklar` tam
olarak bir kez duraklıyordu, `SahteKaynak` `kalan` alanında yazan sayı kadar duraklıyor. Bu
sayı gerçek bir programda bir donanım kesmesinin, bir işletim sistemi bildiriminin ya da bir
ağ paketinin gelişinin karşılığı olurdu; burada yalnız bir tam sayı ve kaynağın **davranışını**
belirliyor — davranışını temsil ettiği gerçek olayı değil. `veriyi_oku`, bu kaynağı bekleyen
bir `async fn`: `kaynak.await` bitene kadar kendi de ilerlemiyor, tıpkı önceki derste içteki
bir geleceği bekleyen dış gövdenin yaptığı gibi.

`SahteKaynak`in `poll` yöntemi her `Pending` dönüşünde `cx.waker().wake_by_ref()` çağırıyor —
tıpkı `BirKezDuraklar` gibi. Bu, gerçek bir kaynakla arasındaki en önemli fark: gerçek bir ağ
soketi kendi kendini uyandırmaz, işletim sisteminin bir bildirim mekanizması (soket hazır
olduğunda çalışma zamanına haber veren bir alt sistem) `Waker`i çağırır. `SahteKaynak` bu
dış bildirimi taklit ediyor — kendi zamanlayıcısı olmadığı için, kendini her seferinde hemen
uyandırıp bir sonraki `poll` çağrısında `kalan`ı bir azaltmayı tercih ediyor. Sonuç, dışarıdan
bakıldığında aynı: yürütücü `Pending` görüyor, bir süre sonra tekrar yoklama fırsatı buluyor,
kaynak sonunda hazır oluyor.

#### Kaç Yoklama, Ne Kadar Bekleme

Aşağıdaki program üç farklı gecikmeyle kurulmuş kaynakları koşturuyor: biri kısa (bir yoklama
bekliyor), biri uzun (dört yoklama bekliyor), biri de bir `async fn` içinden bekleniyor.

```rust
// girdi-cikti.rs — hazir olmayan bir kaynagin kac yoklamada tamamlandigi.
mod ilerle;
use ilerle::{kostur, veriyi_oku, SahteKaynak};
use std::sync::mpsc::channel;

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

    let (_deger_a, yoklama_a) = kostur(SahteKaynak {
        kalan: 1,
        ad: "kaynak-kisa-bekleme",
        kanal: gonder.clone(),
    });
    let (_deger_b, yoklama_b) = kostur(SahteKaynak {
        kalan: 4,
        ad: "kaynak-uzun-bekleme",
        kanal: gonder.clone(),
    });

    let kaynak_c = SahteKaynak {
        kalan: 2,
        ad: "kaynak-ic",
        kanal: gonder.clone(),
    };
    let (_deger_c, yoklama_c) = kostur(veriyi_oku(kaynak_c, gonder.clone()));

    drop(gonder);
    let mut kayitlar: Vec<String> = al.try_iter().collect();
    kayitlar.sort();

    println!("kisa bekleme  (kalan=1) -> {} yoklamada tamamlandi", yoklama_a);
    println!("uzun bekleme  (kalan=4) -> {} yoklamada tamamlandi", yoklama_b);
    println!("okuma, icteki kaynagi bekliyor -> {} yoklamada tamamlandi", yoklama_c);
    println!();
    println!("kayitlar ({}):", kayitlar.len());
    for k in &kayitlar {
        println!("  {}", k);
    }
}
```

```
kisa bekleme  (kalan=1) -> 2 yoklamada tamamlandi
uzun bekleme  (kalan=4) -> 5 yoklamada tamamlandi
okuma, icteki kaynagi bekliyor -> 3 yoklamada tamamlandi

kayitlar (4):
  kaynak-ic
  kaynak-kisa-bekleme
  kaynak-uzun-bekleme
  okuma-tamamlandi
```

**AS5.** Yoklama sayısı, `kalan` alanının başlangıç değerinden bire fazla: `kalan: 1` iki
yoklamada, `kalan: 4` beş yoklamada tamamlanıyor. Kural, Async/Await dersindeki $n+1$ kuralıyla
aynı aileden — orada $n$ ardışık duraklama noktası vardı, burada $n$ ardışık `Pending` dönüşü
var; ikisi de son çağrıyı, `Ready` dönen çağrıyı, sayıya dahil ediyor. `kaynak-ic` (`kalan:
2`), `veriyi_oku` içinden bekleniyor ve üç yoklamada tamamlanıyor — iki `Pending` artı bir
`Ready`, tıpkı doğrudan `kostur`a verilseydi tamamlayacağı sayı. Bu beş ayrı koşumda birebir
aynı kaldı.

#### Bekleyen Görevin Sınıfı

`kaynak-kisa-bekleme` ve `kaynak-uzun-bekleme` **yoklanınca** sınıfına giriyor: ikisi de
`kostur`a doğrudan veriliyor, ikisi de kendi `kalan` sayaçlarına göre ilerliyor. `veriyi_oku`
ise, önceki dersteki `disariyi_bekle` ile aynı konuma düşüyor — kendi ilerlemesi `kaynak_c`nin
tamamlanmasına bağlı, dolayısıyla **başkası bitince** sınıfına giriyor. `kaynak_c`nin kendisi
yine **yoklanınca**; iki sınıf burada da, önceki derste olduğu gibi, aynı görev ağacının farklı
katmanlarında bir arada bulunuyor.

Standart Kütüphane ve Ağ Uygulamaları kursu bir ağ sınırını geçen değerin **tip bilgisini**
neyin koruyup neyin kaybettiğini ölçmüştü — sınırın bir tarafındaki tip, öbür tarafa hangi
biçimde ulaşıyordu. Bu ders aynı sınırı başka bir eksenden ölçüyor: bir girdi/çıktı kaynağını
beklerken bir görevin **ilerlemek için neye ihtiyaç duyduğu**. Tip bilgisi burada hiç
tartışılmıyor — `SahteKaynak`in ürettiği değer zaten en baştan `&'static str`; sınır, bir
tipin biçimini değil, bir görevin ilerleme koşulunu ayırıyor.

İki ölçüm de aynı sınırı — bir programın kendi kontrolündeki kod ile dışarıdan gelen bir
kaynak arasındaki sınırı — konu ediniyor, ama sordukları soru farklı. Orada soru "bu sınırı
geçen değer hâlâ aynı tipte mi" idi; burada soru "bu sınırın öbür tarafındaki kaynak hazır
olana kadar bu taraf ne yapıyor" oluyor. `kaynak-kisa-bekleme` ile `kaynak-uzun-bekleme`
arasındaki fark bir tip farkı değil — ikisi de aynı `SahteKaynak` tipinden, ikisinin de
`Output`u aynı; fark yalnız `kalan` alanının başlangıç değerinde, yani ilerleme koşulunun ne
kadar sıklıkla karşılanacağında.

#### Yürütücü Burada da Fark Etmiyor

Yürütücüler dersi, iki farklı yürütücü kurgusunun aynı görevi aynı sayıda yokladığını
göstermişti. Aynı sonuç, modellenen bir girdi/çıktı kaynağı için de geçerli. Aşağıdaki kesit,
önceki dersteki `kostur_dongu`yu bu derse taşıyıp aynı `SahteKaynak`i iki farklı yürütücüyle
koşturuyor.

```rust
// ilerle.rs — cekirdegin kesiti degisti: onceki dersteki ikinci yurutucu (kostur_dongu)
// buraya taniniyor, ayni SahteKaynak iki yurutucuyle karsilastiriliyor.

use std::future::Future;
use std::pin::Pin;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};

pub struct Uyandirici {
    pub hazir: Mutex<bool>,
    pub kosul: Condvar,
}

impl Wake for Uyandirici {
    fn wake(self: Arc<Self>) {
        *self.hazir.lock().unwrap() = true;
        self.kosul.notify_one();
    }
}

pub fn kostur<F: Future>(gorev: F) -> (F::Output, u32) {
    let mut gorev = Box::pin(gorev);
    let u = Arc::new(Uyandirici {
        hazir: Mutex::new(false),
        kosul: Condvar::new(),
    });
    let waker: Waker = u.clone().into();
    let mut baglam = Context::from_waker(&waker);
    let mut yoklama = 0;
    loop {
        yoklama += 1;
        match gorev.as_mut().poll(&mut baglam) {
            Poll::Ready(x) => return (x, yoklama),
            Poll::Pending => {
                let mut h = u.hazir.lock().unwrap();
                while !*h {
                    h = u.kosul.wait(h).unwrap();
                }
                *h = false;
            }
        }
    }
}

pub struct BosUyandirici;

impl Wake for BosUyandirici {
    fn wake(self: Arc<Self>) {}
}

pub fn kostur_dongu<F: Future>(gorev: F) -> (F::Output, u32) {
    let mut gorev = Box::pin(gorev);
    let u = Arc::new(BosUyandirici);
    let waker: Waker = u.clone().into();
    let mut baglam = Context::from_waker(&waker);
    let mut yoklama = 0;
    loop {
        yoklama += 1;
        if let Poll::Ready(x) = gorev.as_mut().poll(&mut baglam) {
            return (x, yoklama);
        }
    }
}

pub struct SahteKaynak {
    pub kalan: u32,
    pub ad: &'static str,
    pub kanal: Sender<String>,
}

impl Future for SahteKaynak {
    type Output = &'static str;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
        if self.kalan == 0 {
            self.kanal.send(self.ad.to_string()).unwrap();
            Poll::Ready(self.ad)
        } else {
            self.kalan -= 1;
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}
```

```rust
// iki-yurutucu-io.rs — ayni modellenen kaynak, iki farkli yurutucu, ayni yoklama sayisi.
mod ilerle;
use ilerle::{kostur, kostur_dongu, SahteKaynak};
use std::sync::mpsc::channel;

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

    let (_deger_1, yoklama_1) = kostur(SahteKaynak {
        kalan: 3,
        ad: "kaynak-kostur",
        kanal: gonder.clone(),
    });
    let (_deger_2, yoklama_2) = kostur_dongu(SahteKaynak {
        kalan: 3,
        ad: "kaynak-dongu",
        kanal: gonder.clone(),
    });

    drop(gonder);
    let mut kayitlar: Vec<String> = al.try_iter().collect();
    kayitlar.sort();

    println!("kostur={} kostur_dongu={} ayni mi: {}", yoklama_1, yoklama_2, yoklama_1 == yoklama_2);
    println!("kayitlar ({}):", kayitlar.len());
    for k in &kayitlar {
        println!("  {}", k);
    }
}
```

```
kostur=4 kostur_dongu=4 ayni mi: true
kayitlar (2):
  kaynak-dongu
  kaynak-kostur
```

**AS6.** Aynı `kalan: 3` değeriyle kurulan iki kaynak, iki bambaşka yürütücüyle koşturulduğunda
birebir aynı yoklama sayısını (4) veriyor. `SahteKaynak`in `poll` yöntemi hangi yürütücünün
onu çağırdığını hiç bilmiyor — yalnız kendi `kalan` alanına bakıyor. Bu, önceki dersteki
bulguyu modellenen bir girdi/çıktı kaynağına taşıyor: yürütücü, bir kaynağın **ne kadar**
bekleteceğine karışmıyor, yalnız beklerken çağıran iş parçacığının ne yaptığına karışıyor —
ve bu ikinci fark, kursun basmayı yasakladığı bir alanda kalıyor.

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

Modellenen bekleme, gerçek bir gecikmenin süresini göstermiyor. `kalan: 4` alanı beş yoklama
gerektiriyor, ama bu beş çağrı arasında gerçekte hiçbir bekleme yok — `kostur`un koşul
değişkeni her `wake_by_ref` çağrısında hemen uyanıyor, çünkü `SahteKaynak` kendini her
`Pending` dönüşünde kendisi uyandırıyor. Gerçek bir kaynak (bir ağ soketi, bir dosya
tanıtıcısı) bu uyandırmayı kendi hazır olduğu anda, işletim sisteminin bir bildirimiyle
yapardı; burada bu bildirim `kalan` sayacının azalmasıyla taklit ediliyor. Ölçülen şey her
zaman **yoklama sayısı** — bekleme süresi değil, çünkü süre ortama bağlı ve bu kursun
başından beri basılmayan bir değer.

Bu sınırın bir sonucu da şu: `kalan` alanına yazılan sayı büyütülürse yoklama sayısı da
orantılı büyür, ama koşumun gerçek süresi ölçülebilir biçimde değişmez — hiçbir yerde gerçek
bir bekleme yok, yalnız döngünün kaç kez çalıştığı değişiyor. Gerçek bir programda bu ilişki
tersine döner: yoklama sayısı sabit kalabilir (bir yürütücü, bir kaynağı yalnız gerçekten
hazır olduğunda tekrar yoklayacak biçimde tasarlanabilir), ama gerçek bekleme süresi kaynağın
kendi hızına bağlı olarak değişir. Bu iki eksenin — yoklama sayısı ve bekleme süresi —
birbirinden bağımsız olabileceğini görmek, modelin neyi temsil edip neyi etmediğini anlamak
için gerekli: bu ders yalnız birincisini, kursun en başından beri basılmasına izin verilen
tek sayıyı ölçüyor.

#### Özet

- Girdi/çıktı beklerken duraklama, hazır olmayan bir kaynağı modelleyen bir gelecekle
  kurulur: kaynak hazır değilse `Pending` döner ve kendini uyandırır.
- Bir kaynağın kaç yoklamada tamamlanacağı, kaynağın kendi `kalan` mantığından gelir ve
  belirlenimcidir — beş ayrı koşumda birebir aynı kalır.
- Doğrudan yoklanan bir kaynak **yoklanınca** sınıfına girer; onu bekleyen bir `async fn`in
  kendi tamamlanması **başkası bitince** sınıfına girer.
- Standart Kütüphane ve Ağ Uygulamaları kursu bir sınırı tip bilgisi üzerinden ölçtü; bu ders
  aynı sınırı bir görevin ilerleme koşulu üzerinden ölçer.
- Modellenen bekleme gerçek gecikmeyi göstermez; ölçülen şey yoklama sayısıdır, geçen süre
  değil.
- Aynı modellenen kaynak iki farklı yürütücüyle koşturulduğunda da aynı yoklama sayısını
  verir — Yürütücüler dersinin bulgusu, modellenen girdi/çıktıda da değişmeden geçerlidir.

#### Sonraki Adım

Bu konu, bir görevin ilerlemek için neye ihtiyaç duyduğunu dört adımda kurdu: bir görev
kurulmasıyla ilerlemez, yalnız yoklandığında ilerler; `async`/`await` bu yoklamaların nerede
duraklayıp nereden devam edeceğini kaynakta görünür kılar; bir yürütücü bu yoklamaların **ne
zaman** olacağına karar verir ama görevin ürettiği değere karışmaz; ve bir girdi/çıktı kaynağı
bu yoklama zincirini, hazır olma süresine bağlı bir sayıyla uzatabilir. Dört ders boyunca
ölçülen tek sayı — kaç kez `poll` çağrıldığı — her defasında kaynaktan önceden çıkarılabilir
kaldı.

Kalan soru, kaynakta yazılı ama hiç tamamlanmayan ya da tamamlanmadan bırakılan bir görevin ne
olacağı. Kursun kapanış dersi bir görevin **bırakılmasıyla** kurulan iptali ele alıyor:
bırakılan bir görev bir daha hiç yoklanmıyor ve bu dersin `SahteKaynak`i gibi kaynakta yazılı
kalıyor, ama Gelecekler dersindeki `_hic_yoklanmayan` ile aynı akıbeti paylaşıyor — hiç
ilerlemeden. Zaman aşımı da aynı çerçeveden, süreyle değil, yoklama sayısıyla modelleniyor.

### İptal ve Zaman Aşımı

Source: https://academia.sh/tr/kurslar/rust-eszamanlilik/iptal-ve-zaman-asimi

Kursun kapanış dersi bir görevin bırakılmasıyla kurulan iptali ölçüyor: bırakılan görev bir daha yoklanmaz ve hiç sınıfına düşer; zaman aşımı da bir süre değil, tükenen bir yoklama bütçesiyle modellenir.

Eşzamansız Girdi/Çıktı dersi kaynakta yazılı ama hiç tamamlanmayan bir görevin sorusunu açık
bıraktı: bir görev sonsuza kadar duraklayabilir, peki ona bir daha hiç dokunulmazsa ne olur?
Bu kursun kapanış dersi bu soruyu ele alıyor. Rust'ın eşzamansız modelinde bir görevi
durdurmanın ayrı bir çağrısı yok — bir gelecek yalnızca yoklandığı sürece ilerler, bu yüzden
onu bir daha yoklamamak, o görevi iptal etmenin ta kendisi oluyor.

Go'nun Eşzamanlılık kursu iptali bir goroutine'in nasıl duracağını belirleyen dört yoldan
biri, yani bir durma düzeneği olarak ölçtü. Bu ders aynı kavramı başka türlü ölçüyor: iptal
edilen bir görevin kendi başına bir yürütme akışı yok ki dursun — iptal burada bir
**ilerlememe** durumu, kaynakta yazılı kalıp bir daha yoklanmayan bir görevin akıbeti.
Ölçülen iki ayrı yol var: bir görev elle bırakılarak iptal edilebilir, ya da bir yoklama
bütçesi tükendiğinde otomatik olarak bırakılarak zaman aşımına uğrayabilir. İkisi de aynı
sınıfa düşüyor.

#### Bırakılan Bir Görev Bir Daha Yoklanmaz

Bir görevin bırakılması tek başına iptal anlamına gelmiyor. Tamamlanmış bir görev de,
ürettiği değer okunduktan sonra bellekte durmaya devam etmiyor — o da bırakılıyor, ama bu
bir temizlik, bir iptal değil. Ayrım, bırakma anında görevin **bitmiş olup olmadığında**:
bitmeden bırakılan bir görev bir daha hiç yoklanmayacak, bitmiş bir görevin zaten yoklanacak
bir şeyi kalmamış durumda. Aşağıdaki çekirdek kesiti bu ayrımı iki alanla tutuyor:
`baslatildi` görevin en az bir kez yoklandığını, `tamamlandi` son değerini üretip
üretmediğini işaretliyor. Yalnızca ilki doğru, ikincisi yanlışken bırakılan bir görev iptal
edilmiş sayılıyor; bu, görevin kendi `Drop` uygulamasında bir kayıt gönderilerek
ölçülüyor.

```rust
// ilerle.rs — cekirdegin kesiti: birakma ile kurulan iptal ve yoklama butcesiyle
// modellenen zaman asimi.

use std::future::Future;
use std::pin::Pin;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};

pub struct Uyandirici {
    pub hazir: Mutex<bool>,
    pub kosul: Condvar,
}

impl Wake for Uyandirici {
    fn wake(self: Arc<Self>) {
        *self.hazir.lock().unwrap() = true;
        self.kosul.notify_one();
    }
}

pub fn kostur<F: Future>(gorev: F) -> (F::Output, u32) {
    let mut gorev = Box::pin(gorev);
    let u = Arc::new(Uyandirici {
        hazir: Mutex::new(false),
        kosul: Condvar::new(),
    });
    let waker: Waker = u.clone().into();
    let mut baglam = Context::from_waker(&waker);
    let mut yoklama = 0;
    loop {
        yoklama += 1;
        match gorev.as_mut().poll(&mut baglam) {
            Poll::Ready(x) => return (x, yoklama),
            Poll::Pending => {
                let mut h = u.hazir.lock().unwrap();
                while !*h {
                    h = u.kosul.wait(h).unwrap();
                }
                *h = false;
            }
        }
    }
}

// bir kez yoklandiktan sonra elle birakilabilen gorev: baslamis is olculebilir,
// tamamlanma ise ancak ikinci yoklama gerceklesirse kayda geciyor.
pub struct IptalEdilebilirIs {
    pub ad: &'static str,
    pub baslatildi: bool,
    pub tamamlandi: bool,
    pub kanal: Sender<String>,
}

impl Future for IptalEdilebilirIs {
    type Output = &'static str;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
        if !self.baslatildi {
            self.baslatildi = true;
            self.kanal.send(format!("{}-baslatildi", self.ad)).unwrap();
            cx.waker().wake_by_ref();
            Poll::Pending
        } else {
            self.tamamlandi = true;
            self.kanal.send(format!("{}-tamamlandi", self.ad)).unwrap();
            Poll::Ready(self.ad)
        }
    }
}

impl Drop for IptalEdilebilirIs {
    fn drop(&mut self) {
        if self.baslatildi && !self.tamamlandi {
            self.kanal.send(format!("{}-birakildi", self.ad)).unwrap();
        }
    }
}

// her yoklamada bir azalan bir sayacla ilerleyen uzun bir is; tamamlanmadan
// birakilirsa kalan miktar sifirdan buyuk kalir.
pub struct UzunIs {
    pub kalan: u32,
    pub ad: &'static str,
    pub kanal: Sender<String>,
}

impl Future for UzunIs {
    type Output = &'static str;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
        if self.kalan == 0 {
            self.kanal.send(format!("{}-tamamlandi", self.ad)).unwrap();
            Poll::Ready(self.ad)
        } else {
            self.kalan -= 1;
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

impl Drop for UzunIs {
    fn drop(&mut self) {
        if self.kalan > 0 {
            self.kanal.send(format!("{}-yarida-birakildi", self.ad)).unwrap();
        }
    }
}

// ic gorevi sinirli sayida yoklama hakkiyla saran gorev: hak tukenince ic gorev
// hemen birakilir ve bir daha yoklanmaz.
pub struct ZamanAsimliGorev<F: Future> {
    pub ic: Option<Pin<Box<F>>>,
    pub hak: u32,
    pub ad: &'static str,
    pub kanal: Sender<String>,
}

impl<F: Future> Future for ZamanAsimliGorev<F> {
    type Output = Result<F::Output, ()>;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<F::Output, ()>> {
        if self.hak == 0 {
            self.ic = None;
            self.kanal.send(format!("{}-zaman-asimi", self.ad)).unwrap();
            return Poll::Ready(Err(()));
        }
        self.hak -= 1;
        match self.ic.as_mut().unwrap().as_mut().poll(cx) {
            Poll::Ready(x) => Poll::Ready(Ok(x)),
            Poll::Pending => Poll::Pending,
        }
    }
}
```

```rust
// iptal.rs — birakma ile kurulan iptal ve yoklama butcesiyle modellenen zaman asimi
// tek kosumda.
mod ilerle;
use ilerle::{kostur, IptalEdilebilirIs, UzunIs, ZamanAsimliGorev, Uyandirici};
use std::future::Future;
use std::sync::mpsc::channel;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Waker};

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

    // --- tamamlanan gorev: kostur onu bitene kadar yokluyor ---
    let (sonuc, yoklama) = kostur(IptalEdilebilirIs {
        ad: "gorev-tamamlanan",
        baslatildi: false,
        tamamlandi: false,
        kanal: gonder.clone(),
    });

    // --- iptal edilen gorev: bir kez elle yoklaniyor, sonra birakiliyor ---
    let mut iptal_edilen = Box::pin(IptalEdilebilirIs {
        ad: "gorev-iptal-edilen",
        baslatildi: false,
        tamamlandi: false,
        kanal: gonder.clone(),
    });
    let u = Arc::new(Uyandirici { hazir: Mutex::new(false), kosul: Condvar::new() });
    let waker: Waker = u.clone().into();
    let mut baglam = Context::from_waker(&waker);
    let _ = iptal_edilen.as_mut().poll(&mut baglam);
    drop(iptal_edilen);

    // --- yoklama butcesiyle zaman asimi: biri butce icinde bitiyor, digeri asiyor ---
    let (sonuc_a, but_yoklama_a) = kostur(ZamanAsimliGorev {
        ic: Some(Box::pin(UzunIs { kalan: 1, ad: "is-a", kanal: gonder.clone() })),
        hak: 3,
        ad: "is-a",
        kanal: gonder.clone(),
    });
    let (sonuc_b, but_yoklama_b) = kostur(ZamanAsimliGorev {
        ic: Some(Box::pin(UzunIs { kalan: 5, ad: "is-b", kanal: gonder.clone() })),
        hak: 2,
        ad: "is-b",
        kanal: gonder.clone(),
    });

    drop(gonder);
    let mut kayitlar: Vec<String> = al.try_iter().collect();
    kayitlar.sort();

    println!("gorev-tamamlanan {} yoklamada bitti, sonuc: {}", yoklama, sonuc);
    println!("gorev-iptal-edilen 1 yoklamadan sonra elle birakildi");
    println!();
    println!("is-a (hak=3, kalan=1) -> {} yoklamada sonuc: {:?}", but_yoklama_a, sonuc_a);
    println!("is-b (hak=2, kalan=5) -> {} yoklamada sonuc: {:?}", but_yoklama_b, sonuc_b);
    println!();
    println!("kayitlar ({}):", kayitlar.len());
    for k in &kayitlar {
        println!("  {}", k);
    }
}
```

```
gorev-tamamlanan 2 yoklamada bitti, sonuc: gorev-tamamlanan
gorev-iptal-edilen 1 yoklamadan sonra elle birakildi

is-a (hak=3, kalan=1) -> 2 yoklamada sonuc: Ok("is-a")
is-b (hak=2, kalan=5) -> 3 yoklamada sonuc: Err(())

kayitlar (7):
  gorev-iptal-edilen-baslatildi
  gorev-iptal-edilen-birakildi
  gorev-tamamlanan-baslatildi
  gorev-tamamlanan-tamamlandi
  is-a-tamamlandi
  is-b-yarida-birakildi
  is-b-zaman-asimi
```

**AS7.** `gorev-tamamlanan` iki yoklamada bitiyor — bir duraklama, bir tamamlanma — ve
`async`/`await` dersinden tanıdık $n+1$ kuralını sürdürüyor. `gorev-iptal-edilen` yalnız bir
kez elle yoklanıyor, `-baslatildi` kaydını gönderiyor, sonra doğrudan bırakılıyor; bu bırakma
`Drop` uygulamasını tetikliyor ve `-birakildi` kaydını gönderiyor. Bu iki kayıt görevin
**başlamış işini** ölçülebilir kılıyor: bir yoklama gerçekleşti, bir temizlik çalıştı. Ama
`gorev-iptal-edilen-tamamlandi` kaydı yedi kayıt arasında hiç yok — çünkü ikinci yoklama hiç
gerçekleşmedi. İptal edilen görev **hiç** sınıfına giriyor: kaynakta yazılı, bir kez
dokunulmuş, ama tamamlanmadan bırakılmış.

#### Yoklama Bütçesi Tükenince Zaman Aşımı

Zaman aşımı bu kursta bir süreyle değil, bir sayıyla kuruluyor: yukarıdaki kesitteki
`ZamanAsimliGorev`, sardığı görevi en fazla `hak` kez yoklamaya izin veriyor. Bütçe
tükenmeden görev kendi kendine biterse sonuç `Ok` değeriyle dönüyor; bütçe tükendiğinde
`ZamanAsimliGorev` içindeki görevi hemen bırakıyor — `self.ic` alanına `None` atanıyor — ve
bir daha ona dokunmuyor. Bu, önceki bölümdeki elle bırakma düzeneğinin otomatikleştirilmiş
biçimi: kararı görevin kendisi değil, onu saran bütçe veriyor.

**AS8.** `is-a` üç yoklama hakkıyla kuruldu ve yalnızca ikisini kullandı, `Ok("is-a")` ile
bitti. `is-b` yalnızca iki yoklama hakkıyla kuruldu ama beş yoklama gerektiriyordu; üçüncü
çağrıda bütçe sıfıra düştüğü için sarmalayıcı içteki görevi bırakıyor ve `Err(())`
döndürüyor. İçteki görevin kendi `Drop` uygulaması `-yarida-birakildi` kaydını gönderiyor,
çünkü bırakıldığı anda hâlâ üç birimlik işi kalmıştı — beş birimlik işin ikisi tamamlanmış,
üçü **başlamış ama bitmemiş** durumda kalmıştı. `is-b-tamamlandi` kaydı da yedi kayıt
arasında hiç yok. `is-a` **yoklanınca** sınıfına giriyor; `is-b` bütçesi tükendiğinde **hiç**
sınıfına düşüyor — elle bırakılan görevle aynı akıbeti, farklı bir tetikleyiciyle
paylaşıyor.

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

Bırakma, görevin **iç durumunu** göstermiyor. `Drop` uygulaması yalnızca yazması için elle
kodlanmış olanı kaydediyor — `IptalEdilebilirIs` `baslatildi` ile `tamamlandi` bayraklarını,
`UzunIs` kalan sayaç değerini tutuyor. Bu, dilin genel bir "ne kadar iş yapıldı" bilgisini
kendiliğinden açığa çıkarmadığı anlamına geliyor: bir görev türünün yazarı bu bilgiyi
ölçülebilir kılmazsa, bırakılan bir değerin ne durumda olduğu kaynak kod dışında bilinemez.

Yoklama bütçesi de bir süre değil. `hak` alanı kaç kez yoklanacağının bir üst sınırı, ama bir
görevin gerçek dünyada ne kadar sürdüğüyle hiçbir ilişkisi yok — aynı bütçe, her yoklaması
ağır bir görevle de, her yoklaması anlık bir görevle de aynı sayıda yoklama sonra tükeniyor.
Gerçek bir zaman aşımı düzeneği bu sayıyı bir saatle eşleştirir; bu kurs saat kullanmadığı
için, zaman aşımı burada yalnızca **kaç kez denendiğinin** bir ölçüsü, ne kadar beklendiğinin
değil.

#### Kapanış Tablosu

| Ders | Ölçülen birim | İlerleme koşulu | Sınırlayıcı ölçüm |
|---|---|---|---|
| İş Parçacığı Oluşturma | İş parçacığı başlatma ve birleştirme | kendi başına · başkası bitince | Kayıtların sırası ölçülmez, yalnız beklenen sayı var mı ölçülür |
| Kanallarla Mesajlaşma | Kanaldan geçen iletinin sahipliği ve alımı | kendi başına · başkası bitince | Bekleyen taraf süresiz bekler; eksik kayıt beklenen-gelen farkıyla ölçülür |
| İş Parçacıkları Arası Güvenliğin Tipteki Karşılığı | Sınırı geçebilen `Arc<Mutex<...>>` değeri | kendi başına · başkası bitince | Tipin izni veri yarışını engeller, sıraya bağlı doğruluğu garanti etmez |
| Muteks ve Okuma/Yazma Kilidi | Kilit bekleyen taraf | kendi başına · başkası bitince | İki bekleyenden hangisi önce girer ölçülmez, kilit yalnız veriyi korur |
| Atomik İşlemler ve Bellek Sıralaması | Kilitsiz atomik sayaç | kendi başına | Atomik olmak sıralı olmak değildir; hangi artış önce ölçülmez |
| Gelecekler (futures) | Yoklanan ve hiç yoklanmayan görev | yoklanınca · hiç | Yoklanmayan görev hata değildir, sessizce iz bırakmaz |
| `async`/`await` | Duraklama noktası ve yoklama sayısı | yoklanınca · başkası bitince | Duraklama sayısı ölçülür, ne kadar duraklayacağı ölçülmez |
| Yürütücüler | Aynı görev, iki yürütücü kurgusu | yoklanınca | Yürütücü ne zaman yoklayacağına karışır, görevin gövdesine karışmaz |
| Eşzamansız Girdi/Çıktı | Modellenmiş girdi/çıktı kaynağı | yoklanınca · başkası bitince | Modellenen bekleme gerçek gecikmeyi göstermez, ölçülen yoklama sayısı |
| İptal ve Zaman Aşımı | Bırakılan ve zaman aşımına uğrayan görev | hiç | Bırakma iç durumu göstermez; bütçe keyfi bir yoklama sınırıdır |

#### Özet

- Bir görevi bırakmak, Rust'ın eşzamansız modelindeki iptal çağrısıdır; bırakılan görev bir
  daha yoklanmaz ve **hiç** sınıfına düşer.
- Bir bırakma yalnızca görev tamamlanmadan önce gerçekleşirse iptaldir; tamamlanmış bir
  görevin bırakılması sıradan bir temizliktir — iki durum bu derste ayrı kayıtlarla ayrıştı.
- Zaman aşımı bir süre değil bir yoklama bütçesiyle modellendi: bütçesi tükenen görev hemen
  bırakıldı ve bir daha yoklanmadı — o da **hiç** sınıfına düştü.
- İptal edilen görevlerin başlamış işi ölçülebilir kaldı — başladığını ve bırakıldığını
  gösteren kayıtlar vardı — ama tamamlandığını gösteren kayıt hiçbir zaman gelmedi.
- Go'nun Eşzamanlılık kursu iptali bir durma düzeneği olarak ölçtü; bu ders aynı kavramı bir
  ilerlememe durumu olarak ölçtü.
- Kapanış tablosu, kursun on dersini ve dört sınıfını (kendi başına, yoklanınca, başkası
  bitince, hiç) tek bir bakışta topladı.

#### Kurs Kapanışı

Bu kurs on ders boyunca tek bir soruyu sürdürdü: bir birim ilerlemek için neye ihtiyaç
duyuyor. Yanıt hep aynı dört isimden biriydi ve kapanış tablosu bu dört ismin on farklı
birimde nasıl dağıldığını gösterdi — iş parçacıkları kendi başlarına ya da birbirlerini
bekleyerek ilerledi, kilitler ve kanallar bu bekleyişi taşıdı, gelecekler yalnız
yoklandıklarında ilerledi ve bu son ders bir görevin hiç ilerlemeyebileceğini — kaynakta
yazılı kalıp bir daha dokunulmayarak — gösterdi. Bu, deponun beşinci ve şimdilik son
eşzamanlılık kursuydu; beş kurs birlikte aynı alanı beş farklı soruyla dolaştı.

Sıradaki kurs, Rust Ekosistemi ve İleri Konular, dilin eşzamanlılık dışındaki geri kalanına
bakıyor: makrolar, derleyici güvenliğinin gönüllü olarak dışına çıkılan kod kesimleri, başka
dillerle kurulan çağrı arayüzleri ve bir Rust projesinin paket yönetiminden yayınına kadarki
yaşam döngüsü. Bu derste ölçülen ilerleme sorusu — bir birimin ilerlemek için neye ihtiyaç
duyduğu — orada yeniden sorulmuyor; sıradaki kurs kendi ölçü eksenini kendi konusundan
kuracak.

