Zarif Kapanış (Graceful Shutdown) ve Temizlik
Liste 21-20’deki kod, amaçladığımız gibi bir thread (iş parçacığı) havuzu kullanarak isteklere birbirini bekletmeden (eşzamansız biçimde) yanıt veriyor. Doğrudan kullanmadığımız calisanlar, kimlik ve thread alanları hakkında bazı uyarılar alıyoruz; bu uyarılar bize henüz hiçbir temizlik işlemi yapmadığımızı hatırlatıyor. Ana thread’i durdurmak için daha kaba olan ctrl-C yöntemini kullandığımızda, diğer tüm thread’ler bir isteğe yanıt vermenin tam ortasında olsalar bile anında durduruluyor.
Şimdi, havuzdaki her bir thread üzerinde join çağırmak için Drop trait’ini uygulayacağız; böylece thread’ler kapanmadan önce üzerinde çalıştıkları istekleri tamamlayabilecekler. Ardından, thread’lere yeni istek kabul etmeyi bırakıp kapanmaları gerektiğini bildirecek bir mekanizma kuracağız. Bu kodu çalışırken görmek için sunucumuzu, thread havuzunu zarif biçimde kapatmadan önce yalnızca iki isteği kabul edecek şekilde değiştireceğiz.
İlerlerken dikkat etmeniz gereken bir nokta var: Buradaki değişikliklerin hiçbiri kapanışları (closures) çalıştıran kod bölümlerini etkilemez; dolayısıyla bir asenkron çalışma zamanı (async runtime) için thread havuzu yazıyor olsaydık da buradaki her şey birebir aynı olurdu.
IsParcacigiHavuzu Üzerinde Drop Trait’ini Uygulamak
Thread havuzumuz üzerinde Drop uygulayarak başlayalım. Havuz düşürüldüğünde, yürüttükleri işleri tamamladıklarından emin olmak için tüm thread’lerimizi join ile beklememiz gerekir. Liste 21-22, Drop uygulamasına yönelik ilk denemeyi gösterir; bu kod henüz tam olarak çalışmayacaktır.
use std::{
sync::{Arc, Mutex, mpsc},
thread,
};
pub struct IsParcacigiHavuzu {
calisanlar: Vec<Calisan>,
gonderici: mpsc::Sender<Gorev>,
}
type Gorev = Box<dyn FnOnce() + Send + 'static>;
impl IsParcacigiHavuzu {
/// Yeni bir IsParcacigiHavuzu oluşturur.
///
/// Boyut, havuzdaki thread sayısıdır.
///
/// # Panics
///
/// `new` fonksiyonu, boyut sıfırsa panikler.
pub fn new(boyut: usize) -> IsParcacigiHavuzu {
assert!(boyut > 0);
let (gonderici, alici) = mpsc::channel();
let alici = Arc::new(Mutex::new(alici));
let mut calisanlar = Vec::with_capacity(boyut);
for kimlik in 0..boyut {
calisanlar.push(Calisan::new(kimlik, Arc::clone(&alici)));
}
IsParcacigiHavuzu {
calisanlar,
gonderici,
}
}
pub fn calistir<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let gorev = Box::new(f);
self.gonderici.send(gorev).unwrap();
}
}
impl Drop for IsParcacigiHavuzu {
fn drop(&mut self) {
for calisan in &mut self.calisanlar {
println!("Çalışan kapatılıyor: {}", calisan.kimlik);
calisan.thread.join().unwrap();
}
}
}
struct Calisan {
kimlik: usize,
thread: thread::JoinHandle<()>,
}
impl Calisan {
fn new(kimlik: usize, alici: Arc<Mutex<mpsc::Receiver<Gorev>>>) -> Calisan {
let thread = thread::spawn(move || loop {
let gorev = alici.lock().unwrap().recv().unwrap();
println!("Çalışan {kimlik} bir görev aldı; çalıştırılıyor.");
gorev();
});
Calisan { kimlik, thread }
}
}
join ile beklemekİlk olarak thread havuzunun calisanlar vektöründeki her öğe üzerinde döngü kurarız. Bunun için &mut kullanırız; çünkü self değiştirilebilir bir referanstır ve calisan örneğini de değiştirebilmemiz gerekir. Her calisan için bu Calisan örneğinin kapatıldığını belirten bir mesaj yazdırır ve ardından o Calisan örneğinin thread’i üzerinde join çağırırız. join çağrısı başarısız olursa, Rust’ın panikleyip zarif olmayan bir kapanışa geçmesi için unwrap kullanırız.
Bu kodu derlediğimizde şu hatayı alırız:
$ cargo check
Checking merhaba v0.1.0 (file:///projects/merhaba)
error[E0507]: cannot move out of `calisan.thread` which is behind a mutable reference
--> src/lib.rs:55:13
|
55 | calisan.thread.join().unwrap();
| ^^^^^^^^^^^^^^ ------ `calisan.thread` moved due to this method call
| |
| move occurs because `calisan.thread` has type `JoinHandle<()>`, which does not implement the `Copy` trait
|
note: `std::thread::JoinHandle::<T>::join` takes ownership of the receiver `self`, which moves `calisan.thread`
--> /rustc/58cf8f9c35b355199727627d07ae34a4e2846d67/library/std/src/thread/join_handle.rs:149:16
For more information about this error, try `rustc --explain E0507`.
error: could not compile `merhaba` (lib) due to 1 previous error
Hata bize, her calisan için yalnızca değiştirilebilir bir ödünç almaya sahip olduğumuz ve join metodu üzerinde çağrıldığı JoinHandle değerinin sahipliğini aldığı (self parametresini tükettiği) için join çağıramayacağımızı söyler. Bu sorunu çözmek için, join metodunun thread’i tüketebilmesi amacıyla thread alanının sahibi olan Calisan örneğinin içinden thread’i dışarı taşımamız gerekir. Bunu yapmanın bir yolu, Liste 18-15’te izlediğimiz yaklaşımın aynısını uygulamaktır. Calisan, bir Option<thread::JoinHandle<()>> tutsaydı, değeri Some varyantının dışına taşıyıp yerinde None varyantı bırakmak için Option üzerinde take metodunu çağırabilirdik. Başka bir deyişle, çalışmakta olan bir Calisan, thread alanında bir Some varyantı bulundurur; bir Calisan örneğini temizlemek istediğimizde ise Calisan’ın çalıştıracak bir thread’i kalmaması için Some yerine None koyardık.
Ancak bu durum yalnızca Calisan düşürülürken karşımıza çıkardı. Buna karşılık, calisan.thread alanına eriştiğimiz her yerde bir Option<thread::JoinHandle<()>> ile uğraşmak zorunda kalırdık. İdiyomatik Rust’ta Option oldukça sık kullanılır; fakat her zaman var olacağını bildiğiniz bir değeri sırf geçici bir çözüm olarak Option içine sardığınızı fark ettiğinizde, kodunuzu daha temiz ve hataya daha az açık hâle getirecek alternatif yaklaşımlar aramak iyi bir fikirdir.
Bu durumda daha iyi bir alternatif mevcuttur: Vec::drain metodu. Vektörden hangi öğelerin çıkarılacağını belirtmek için bir aralık (range) parametresi kabul eder ve bu öğeler üzerinde bir iteratör döndürür. .. aralık sözdizimini geçirmek, vektördeki her değeri çıkaracaktır. Öğeler iteratörden sahipli değerler olarak çıktığı için her calisan üzerinde join çağırabiliriz; döngü bittiğinde calisanlar vektörü boşalmış olur.
Dolayısıyla IsParcacigiHavuzu için drop uygulamasını şöyle güncellememiz gerekir:
#![allow(unused)]
fn main() {
use std::{
sync::{Arc, Mutex, mpsc},
thread,
};
pub struct IsParcacigiHavuzu {
calisanlar: Vec<Calisan>,
gonderici: mpsc::Sender<Gorev>,
}
type Gorev = Box<dyn FnOnce() + Send + 'static>;
impl IsParcacigiHavuzu {
/// Yeni bir IsParcacigiHavuzu oluşturur.
///
/// Boyut, havuzdaki thread sayısıdır.
///
/// # Panics
///
/// `new` fonksiyonu, boyut sıfırsa panikler.
pub fn new(boyut: usize) -> IsParcacigiHavuzu {
assert!(boyut > 0);
let (gonderici, alici) = mpsc::channel();
let alici = Arc::new(Mutex::new(alici));
let mut calisanlar = Vec::with_capacity(boyut);
for kimlik in 0..boyut {
calisanlar.push(Calisan::new(kimlik, Arc::clone(&alici)));
}
IsParcacigiHavuzu {
calisanlar,
gonderici,
}
}
pub fn calistir<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let gorev = Box::new(f);
self.gonderici.send(gorev).unwrap();
}
}
impl Drop for IsParcacigiHavuzu {
fn drop(&mut self) {
for calisan in self.calisanlar.drain(..) {
println!("Çalışan kapatılıyor: {}", calisan.kimlik);
calisan.thread.join().unwrap();
}
}
}
struct Calisan {
kimlik: usize,
thread: thread::JoinHandle<()>,
}
impl Calisan {
fn new(kimlik: usize, alici: Arc<Mutex<mpsc::Receiver<Gorev>>>) -> Calisan {
let thread = thread::spawn(move || loop {
let gorev = alici.lock().unwrap().recv().unwrap();
println!("Çalışan {kimlik} bir görev aldı; çalıştırılıyor.");
gorev();
});
Calisan { kimlik, thread }
}
}
}
Bu değişiklik derleyici hatasını çözer ve kodumuzda başka herhangi bir değişiklik gerektirmez. Unutmayın ki drop, bir panik sırasında da çağrılabildiğinden buradaki unwrap çağrısı da panikleyerek ikili paniğe (double panic) neden olabilir; bu durum programı anında çökertir ve devam eden tüm temizlik işlemlerini yarıda keser. Örnek bir program için bu kabul edilebilirdir, ancak üretim kodu için önerilmez.
Thread’lere Yeni Görev Dinlemeyi Bırakmalarını Bildirmek
Yaptığımız tüm değişikliklerle kodumuz artık hiçbir uyarı vermeden derleniyor. Ancak kötü haber şu ki bu kod henüz istediğimiz biçimde çalışmıyor. Sorunun anahtarı, Calisan örneklerinin thread’leri tarafından çalıştırılan kapanışlardaki mantıktadır: Şu anda join çağırıyoruz, fakat bu çağrı thread’leri kapatmaz; çünkü thread’ler görev aramak için sonsuza dek loop içinde döner. Mevcut drop uygulamamızla IsParcacigiHavuzu örneğimizi düşürmeye çalışırsak, ana thread ilk thread’in bitmesini bekleyerek sonsuza dek bloklanır.
Bu sorunu düzeltmek için önce IsParcacigiHavuzu drop uygulamasında, ardından da Calisan döngüsünde değişiklik yapmamız gerekecek.
İlk olarak IsParcacigiHavuzu drop uygulamasını, thread’lerin bitmesini beklemeden önce gonderici değerini açıkça düşürecek şekilde değiştireceğiz. Liste 21-23, gonderici değerini açıkça düşürmek için IsParcacigiHavuzu üzerinde yapılan değişiklikleri gösterir. thread alanında olduğunun aksine, burada gonderici değerini Option::take ile IsParcacigiHavuzu dışına taşıyabilmek için gerçekten bir Option kullanmamız gerekir.
use std::{
sync::{Arc, Mutex, mpsc},
thread,
};
pub struct IsParcacigiHavuzu {
calisanlar: Vec<Calisan>,
gonderici: Option<mpsc::Sender<Gorev>>,
}
// --snip--
type Gorev = Box<dyn FnOnce() + Send + 'static>;
impl IsParcacigiHavuzu {
/// Yeni bir IsParcacigiHavuzu oluşturur.
///
/// Boyut, havuzdaki thread sayısıdır.
///
/// # Panics
///
/// `new` fonksiyonu, boyut sıfırsa panikler.
pub fn new(boyut: usize) -> IsParcacigiHavuzu {
// --snip--
assert!(boyut > 0);
let (gonderici, alici) = mpsc::channel();
let alici = Arc::new(Mutex::new(alici));
let mut calisanlar = Vec::with_capacity(boyut);
for kimlik in 0..boyut {
calisanlar.push(Calisan::new(kimlik, Arc::clone(&alici)));
}
IsParcacigiHavuzu {
calisanlar,
gonderici: Some(gonderici),
}
}
pub fn calistir<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let gorev = Box::new(f);
self.gonderici.as_ref().unwrap().send(gorev).unwrap();
}
}
impl Drop for IsParcacigiHavuzu {
fn drop(&mut self) {
drop(self.gonderici.take());
for calisan in self.calisanlar.drain(..) {
println!("Çalışan kapatılıyor: {}", calisan.kimlik);
calisan.thread.join().unwrap();
}
}
}
struct Calisan {
kimlik: usize,
thread: thread::JoinHandle<()>,
}
impl Calisan {
fn new(kimlik: usize, alici: Arc<Mutex<mpsc::Receiver<Gorev>>>) -> Calisan {
let thread = thread::spawn(move || loop {
let gorev = alici.lock().unwrap().recv().unwrap();
println!("Çalışan {kimlik} bir görev aldı; çalıştırılıyor.");
gorev();
});
Calisan { kimlik, thread }
}
}
Calisan thread’lerini birleştirmeden önce gonderici değerini açıkça düşürmekgonderici değerinin düşürülmesi kanalı kapatır (bir kanal, tüm göndericileri düşürüldüğünde kapanır; bizim kanalımızın tek göndericisi budur); bu da artık başka mesaj gönderilmeyeceğini belirtir. Bu gerçekleştiğinde, Calisan örneklerinin sonsuz döngü içinde yaptığı tüm recv çağrıları bir hata döndürür. Liste 21-24’te, bu durumda döngüden zarif biçimde çıkacak şekilde Calisan döngüsünü değiştiriyoruz; bu da IsParcacigiHavuzu drop uygulaması üzerlerinde join çağırdığında thread’lerin tamamlanacağı anlamına gelir.
Not: drop metodu &mut self aldığı için self.gonderici alanını drop(self.gonderici) yazarak doğrudan taşıyamayız; bir referansın arkasındaki değerden alan taşımaya çalışmak, Liste 21-22’de gördüğümüz E0507 hatasını üretir. Option::take bu sorunu, Some içindeki göndericiyi alıp yerine None bırakarak çözer; IsParcacigiHavuzu böylece her an geçerli bir durumda kalır. Adımların sırası da önemlidir: Gönderici önce düşürülmeli, thread’ler sonra join ile beklenmelidir. Sırayı tersine çevirirsek gönderici hâlâ hayatta olduğu için recv hiçbir zaman hata döndürmez ve ilk join çağrısı sonsuza dek bloklanır.
use std::{
sync::{Arc, Mutex, mpsc},
thread,
};
pub struct IsParcacigiHavuzu {
calisanlar: Vec<Calisan>,
gonderici: Option<mpsc::Sender<Gorev>>,
}
type Gorev = Box<dyn FnOnce() + Send + 'static>;
impl IsParcacigiHavuzu {
/// Yeni bir IsParcacigiHavuzu oluşturur.
///
/// Boyut, havuzdaki thread sayısıdır.
///
/// # Panics
///
/// `new` fonksiyonu, boyut sıfırsa panikler.
pub fn new(boyut: usize) -> IsParcacigiHavuzu {
assert!(boyut > 0);
let (gonderici, alici) = mpsc::channel();
let alici = Arc::new(Mutex::new(alici));
let mut calisanlar = Vec::with_capacity(boyut);
for kimlik in 0..boyut {
calisanlar.push(Calisan::new(kimlik, Arc::clone(&alici)));
}
IsParcacigiHavuzu {
calisanlar,
gonderici: Some(gonderici),
}
}
pub fn calistir<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let gorev = Box::new(f);
self.gonderici.as_ref().unwrap().send(gorev).unwrap();
}
}
impl Drop for IsParcacigiHavuzu {
fn drop(&mut self) {
drop(self.gonderici.take());
for calisan in self.calisanlar.drain(..) {
println!("Çalışan kapatılıyor: {}", calisan.kimlik);
calisan.thread.join().unwrap();
}
}
}
struct Calisan {
kimlik: usize,
thread: thread::JoinHandle<()>,
}
impl Calisan {
fn new(kimlik: usize, alici: Arc<Mutex<mpsc::Receiver<Gorev>>>) -> Calisan {
let thread = thread::spawn(move || loop {
let mesaj = alici.lock().unwrap().recv();
match mesaj {
Ok(gorev) => {
println!(
"Çalışan {kimlik} bir görev aldı; çalıştırılıyor."
);
gorev();
}
Err(_) => {
println!(
"Çalışan {kimlik} bağlantısı kesildi; kapatılıyor."
);
break;
}
}
});
Calisan { kimlik, thread }
}
}
recv hata döndürdüğünde döngüden açıkça çıkmakBu kodu çalışırken görmek için, Liste 21-25’te gösterildiği gibi sunucuyu zarif biçimde kapatmadan önce yalnızca iki isteği kabul edecek şekilde main fonksiyonunu değiştirelim.
use merhaba::IsParcacigiHavuzu;
use std::{
fs,
io::{BufReader, prelude::*},
net::{TcpListener, TcpStream},
thread,
time::Duration,
};
fn main() {
let dinleyici = TcpListener::bind("127.0.0.1:7878").unwrap();
let havuz = IsParcacigiHavuzu::new(4);
for akis in dinleyici.incoming().take(2) {
let akis = akis.unwrap();
havuz.calistir(|| {
baglantiyi_isle(akis);
});
}
println!("Kapatılıyor.");
}
fn baglantiyi_isle(mut akis: TcpStream) {
let tamponlu_okuyucu = BufReader::new(&akis);
let istek_satiri = tamponlu_okuyucu.lines().next().unwrap().unwrap();
let (durum_satiri, dosya_adi) = match &istek_satiri[..] {
"GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "merhaba.html"),
"GET /sleep HTTP/1.1" => {
thread::sleep(Duration::from_secs(5));
("HTTP/1.1 200 OK", "merhaba.html")
}
_ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
};
let icerik = fs::read_to_string(dosya_adi).unwrap();
let uzunluk = icerik.len();
let yanit =
format!("{durum_satiri}\r\nContent-Length: {uzunluk}\r\n\r\n{icerik}");
akis.write_all(yanit.as_bytes()).unwrap();
}
Gerçek dünyadaki bir web sunucusunun yalnızca iki isteğe yanıt verdikten sonra kapanmasını istemezsiniz. Bu kod yalnızca zarif kapanış ve temizlik mekanizmasının düzgün çalıştığını gösterir.
take metodu Iterator trait’inde tanımlanmıştır ve yinelemeyi en fazla ilk iki öğeyle sınırlar. IsParcacigiHavuzu, main sonunda kapsam dışına çıkacak ve drop uygulaması çalışacaktır.
Sunucuyu cargo run ile başlatın ve üç istek gönderin. Üçüncü istek hata vermeli ve terminalinizde şuna benzer bir çıktı görmelisiniz:
$ cargo run
Compiling merhaba v0.1.0 (file:///projects/merhaba)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.41s
Running `target/debug/merhaba`
Çalışan 0 bir görev aldı; çalıştırılıyor.
Kapatılıyor.
Çalışan kapatılıyor: 0
Çalışan 3 bir görev aldı; çalıştırılıyor.
Çalışan 1 bağlantısı kesildi; kapatılıyor.
Çalışan 2 bağlantısı kesildi; kapatılıyor.
Çalışan 3 bağlantısı kesildi; kapatılıyor.
Çalışan 0 bağlantısı kesildi; kapatılıyor.
Çalışan kapatılıyor: 1
Çalışan kapatılıyor: 2
Çalışan kapatılıyor: 3
Yazdırılan Calisan kimliklerinin ve mesajların sırası sizde farklı olabilir. Bu kodun nasıl çalıştığını mesajlardan görebiliriz: 0 ve 3 numaralı Calisan örnekleri ilk iki isteği aldı. Sunucu ikinci bağlantıdan sonra yeni bağlantı kabul etmeyi bıraktı ve IsParcacigiHavuzu üzerindeki Drop uygulaması, Çalışan 3 henüz görevine bile başlamadan çalışmaya başladı. gonderici değerinin düşürülmesi tüm Calisan örneklerinin bağlantısını keser ve onlara kapanmalarını söyler. Her Calisan örneği bağlantısı kesildiğinde bir mesaj yazdırır ve ardından thread havuzu her bir Calisan thread’inin bitmesini beklemek için join çağırır.
Bu özel çalıştırmanın ilginç bir yönüne dikkat edin: IsParcacigiHavuzu, gonderici değerini düşürdü ve herhangi bir Calisan hata almadan önce Çalışan 0 üzerinde join çağırmayı denedik. Çalışan 0 henüz recv çağrısından bir hata almamıştı; bu yüzden ana thread bloklanarak Çalışan 0’ın bitmesini bekledi. Bu sırada Çalışan 3 bir görev aldı ve ardından tüm thread’ler birer hata aldı. Çalışan 0 işini bitirdiğinde ana thread geri kalan Calisan örneklerinin bitmesini bekledi. O noktada hepsi döngülerinden çıkmış ve durmuştu.
Not: Gerçek Üretim Sunucularında İşletim Sistemi Sinyalleriyle Zarif Kapanış: Gerçek dünyada bir sunucu take(2) ile değil, işletim sisteminden gelen sonlandırma sinyalleriyle (Ctrl+C ile gönderilen SIGINT veya konteyner/servis yöneticilerinin gönderdiği SIGTERM) zarif kapanışa geçer. Senkron Rust sunucularında ctrlc ya da signal-hook crate’leri kullanılarak bir kapatma bayrağı/kanalı tetiklenir ve dinleme döngüsü sonlandırılarak IsParcacigiHavuzu’nun doğal biçimde kapsam dışına çıkıp Drop çalıştırması sağlanır; axum ve tokio tabanlı asenkron sunucularda ise aynı desen tokio::signal::ctrl_c() ve with_graceful_shutdown ile kurulur. Ayrıca üretim kodunda Drop içinde calisan.thread.join().unwrap() yerine if let Err(hata) = calisan.thread.join() { eprintln!(...); } kullanmak, olası bir ikili paniğin (double panic) süreci anında sonlandırmasını önler.
Tebrikler! Artık projemizi tamamladık; birbirini bekletmeden (eşzamansız) yanıt vermek için bir thread havuzu kullanan temel bir web sunucumuz var. Havuzdaki tüm thread’leri temizleyen zarif bir sunucu kapanışı gerçekleştirebiliyoruz.
Referans olması için kodun tamamı aşağıda verilmiştir:
use merhaba::IsParcacigiHavuzu;
use std::{
fs,
io::{BufReader, prelude::*},
net::{TcpListener, TcpStream},
thread,
time::Duration,
};
fn main() {
let dinleyici = TcpListener::bind("127.0.0.1:7878").unwrap();
let havuz = IsParcacigiHavuzu::new(4);
for akis in dinleyici.incoming().take(2) {
let akis = akis.unwrap();
havuz.calistir(|| {
baglantiyi_isle(akis);
});
}
println!("Kapatılıyor.");
}
fn baglantiyi_isle(mut akis: TcpStream) {
let tamponlu_okuyucu = BufReader::new(&akis);
let istek_satiri = tamponlu_okuyucu.lines().next().unwrap().unwrap();
let (durum_satiri, dosya_adi) = match &istek_satiri[..] {
"GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "merhaba.html"),
"GET /sleep HTTP/1.1" => {
thread::sleep(Duration::from_secs(5));
("HTTP/1.1 200 OK", "merhaba.html")
}
_ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
};
let icerik = fs::read_to_string(dosya_adi).unwrap();
let uzunluk = icerik.len();
let yanit =
format!("{durum_satiri}\r\nContent-Length: {uzunluk}\r\n\r\n{icerik}");
akis.write_all(yanit.as_bytes()).unwrap();
}
use std::{
sync::{Arc, Mutex, mpsc},
thread,
};
pub struct IsParcacigiHavuzu {
calisanlar: Vec<Calisan>,
gonderici: Option<mpsc::Sender<Gorev>>,
}
type Gorev = Box<dyn FnOnce() + Send + 'static>;
impl IsParcacigiHavuzu {
/// Yeni bir IsParcacigiHavuzu oluşturur.
///
/// Boyut, havuzdaki thread sayısıdır.
///
/// # Panics
///
/// `new` fonksiyonu, boyut sıfırsa panikler.
pub fn new(boyut: usize) -> IsParcacigiHavuzu {
assert!(boyut > 0);
let (gonderici, alici) = mpsc::channel();
let alici = Arc::new(Mutex::new(alici));
let mut calisanlar = Vec::with_capacity(boyut);
for kimlik in 0..boyut {
calisanlar.push(Calisan::new(kimlik, Arc::clone(&alici)));
}
IsParcacigiHavuzu {
calisanlar,
gonderici: Some(gonderici),
}
}
pub fn calistir<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let gorev = Box::new(f);
self.gonderici.as_ref().unwrap().send(gorev).unwrap();
}
}
impl Drop for IsParcacigiHavuzu {
fn drop(&mut self) {
drop(self.gonderici.take());
for calisan in self.calisanlar.drain(..) {
println!("Çalışan kapatılıyor: {}", calisan.kimlik);
calisan.thread.join().unwrap();
}
}
}
struct Calisan {
kimlik: usize,
thread: thread::JoinHandle<()>,
}
impl Calisan {
fn new(kimlik: usize, alici: Arc<Mutex<mpsc::Receiver<Gorev>>>) -> Calisan {
let thread = thread::spawn(move || {
loop {
let mesaj = alici.lock().unwrap().recv();
match mesaj {
Ok(gorev) => {
println!(
"Çalışan {kimlik} bir görev aldı; çalıştırılıyor."
);
gorev();
}
Err(_) => {
println!(
"Çalışan {kimlik} bağlantısı kesildi; kapatılıyor."
);
break;
}
}
}
});
Calisan { kimlik, thread }
}
}
Burada daha fazlasını da yapabiliriz! Bu projeyi geliştirmeye devam etmek isterseniz işte birkaç fikir:
IsParcacigiHavuzutürüne ve açık (pub) metotlarına daha fazla belgelendirme yorumu ekleyin.- Kütüphanenin işlevselliği için testler ekleyin.
unwrapçağrılarını daha sağlam bir hata yönetimiyle değiştirin.- Web isteklerine yanıt vermek dışında başka bir görevi yerine getirmek için
IsParcacigiHavuzukullanın. - crates.io üzerinde bir thread havuzu crate’i bulun ve bunun yerine o crate’i kullanarak benzer bir web sunucusu yazın. Ardından API’sini ve sağlamlığını kendi uyguladığımız thread havuzuyla karşılaştırın.
Özet
Harika iş çıkardınız! Kitabın sonuna ulaştınız! Bu Rust yolculuğunda bize katıldığınız için teşekkür ederiz. Artık kendi Rust projelerinizi hayata geçirmeye ve başkalarının projelerine katkıda bulunmaya hazırsınız. Rust yolculuğunuzda karşılaşabileceğiniz her türlü zorlukta size yardımcı olmaktan mutluluk duyacak diğer Rustacean’lardan oluşan sıcak ve destekleyici bir topluluk olduğunu her zaman aklınızda bulundurun.