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.