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ü,
Pendingdö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,
Pendingdönüşü ile yeniden yoklama arasında geçen — ve basılmayan — süredeki davranışta yaşar;kosturvekostur_dongugenel (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.