İçeriğe geç
academia.sh

Ders 08 / 10

Yürütücüler

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.

Yayın Güncelleme

Bu derste

  • Bir yürütücünün görevi ne yaptığından bağımsız, yalnız ne zaman yoklanacağına karar veren bir bileşen olduğunu gösterir
  • Aynı görev türünü iki farklı yürütücü kurgusuyla koşturup yoklama sayısının değişmediğini doğrular
  • Bir gövdenin kaç kez koştuğunu sayan Trait'ler, Genellikler ve Hata Yönetimi kursunun ölçtüğü eksenle bu dersin "kim ilerletiyor" sorusu arasındaki farkı açıklar
İçindekiler

Önceki iki ders kosturu 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.

// 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, pollu çağır, sonucu değerlendir. Ayrıldıkları tek yer Pending dönüşünde ne yaptıkları. kostur, Uyandiricinin 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 wakei hiç kullanmıyor; gövdesi boş, çünkü kostur_dongu zaten wake çağrısını beklemeden yeniden yokluyor. Wakerin kendisi hâlâ her iki gelecek türüne veriliyor — BirKezDurakların pollu kendi Contextine 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 Wakerin 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.

// 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 Wakeri ç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.rsdeki 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 pollu 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; pollu ç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 öznedir; 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 — kosturu — yeniden kullanı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