İçeriğe geç
academia.sh

Ders 01 / 10

İş Parçacığı Oluşturma

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.

Yayın Güncelleme

Bu derste

  • Bir birimin kendi başına, başkası bitince ya da hiç ilerlememe durumlarını birbirinden ayırt eder
  • Ölçüm çekirdeğini koşturarak on üç birimin dört sınıfa nasıl dağıldığını doğrular
  • Bu kursun ölçü eksenini önceki eşzamanlılık kurslarının eksenlerinden ayırır
İçindekiler

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

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

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat