00 Korkusuz eşzamanlılık
C'de paylaşımlı belleğe iki thread'in kilitsiz dokunması bir data race'tir ve standart bunu tanımsız davranış (UB) ilan eder — derleyici uyarmaz, sanitizer çoğu zaman yakalamaz, hata üretimde rastgele patlar. Rust'ta aynı kod derlenmez. Veri yarışı bir runtime sorunu değil, bir tip hatasıdır.
pthreads ile çalışırken bildiğiniz disiplin şudur: hangi mutex'in hangi veriyi koruduğunu kafanızda tutarsınız, her erişimden önce kilitlersiniz, kilit sırasına dikkat edip deadlock'tan kaçınırsınız. Bu sözleşmenin tamamı belgelenmemiş, derleyici tarafından denetlenmeyen bir konvansiyondur. Tek bir unutulmuş pthread_mutex_lock sessizce UB üretir.
Rust aynı garantileri sahip olduğu ownership sisteminden bedavaya çıkarır. Bir değere aynı anda ya bir tane &mut (yazıcı) ya da çok sayıda & (okuyucu) erişebilir — bu kural tek thread'de aliasing hatalarını önler; çoklu thread'de ise data race'in tanımının kendisini ortadan kaldırır, çünkü data race = "en az biri yazan, eşzamanlı, senkronizasyonsuz iki erişim".
C/pthread sözleşmesi Rust'ta zorunlu kıldıran ───────────────── ───────────────── "kilitlemeyi unutma" → Mutex erişimi lock() ister "kilit + veri eşleşsin" → veri Mutex'in İÇİNDE "bu tip thread-safe mi?" → Send / Sync trait'leri "dangling thread yok" → 'static + JoinHandle
İki dik mekanizma
Rust eşzamanlılığı iki bağımsız ekseni denetler. Birincisi borrow checker: bir veriye kaç referansın hangi türden eriştiğini izler. İkincisi Send/Sync auto trait'leri: bir tipin thread sınırlarını geçip geçemeyeceğini tip düzeyinde işaretler. std::thread::spawn, kapatma (closure) içine taşınan her şeyin Send + 'static olmasını isteyen tek bir imzayla bütün modeli devreye sokar.
"Korkusuz eşzamanlılık" (fearless concurrency) pazarlama değil, ölçülebilir bir iddia: yanlış senkronizasyon kategorisindeki hataların büyük kısmı derleme aşamasında durur. Geriye kalan asıl risk deadlock'tur — Rust onu engellemez, çünkü deadlock bir bellek güvenliği ihlali değildir.
Bu bölümde
- C'de data race UB'dir ve sessizdir; Rust'ta aynı kod derlenmez
- Borrow checker'ın aliasing kuralı doğrudan data race'i tanımsız kılar
- İki dik eksen: ownership/borrow ve Send/Sync auto trait'leri
- Deadlock derlemeyle engellenmez — o bir mantık hatasıdır, bellek hatası değil
01 thread::spawn ve join
thread::spawn, pthread_create'in yerini tutar ama imzası taban tabana farklı: void* alıp void* döndüren bir C fonksiyonu yerine, doğrudan tipli bir closure alır ve geriye JoinHandle<T> verir — pthread'in void** retval kanalını tip güvenli hale getirir.
Closure'a dışarıdan veri taşımak için move anahtar kelimesi kullanılır: bu, yakalanan değerlerin sahipliğini yeni thread'e geçirir. C'de argümanı void* olarak paketleyip thread'in ömrü boyunca o belleğin yaşadığından elle emin olmanız gerekirdi; Rust'ta move sahipliği devrettiği için derleyici bunu garanti eder.
use std::thread;
fn main() {
let mesaj = String::from("merhaba");
// move: `mesaj`in sahipliği thread'e geçer (pthread'de void* arg)
let tutamac = thread::spawn(move || {
println!("alt thread: {}", mesaj);
mesaj.len() // dönüş değeri JoinHandle<usize> ile taşınır
});
// join() == pthread_join; retval tip güvenli gelir
let uzunluk = tutamac.join().unwrap();
println!("thread {} bayt yazdı", uzunluk);
}FnOnce + Send + 'static bir closure alır, JoinHandle<T> döndürür.T closure'ın dönüş tipidir.Result<T, Box<dyn Any>> verir; Err = panic.JoinHandle üzerinde join() çağırmazsanız thread detach edilmiş olur — main biterse process'le birlikte aniden ölür. C'deki gibi "sessizce arkada koşmaya devam etmez"; bekleneni açıkça join etmelisiniz.
Thread'in panik atması process'i çökertmez: panic o thread'le sınırlı kalır ve join() bir Err olarak geri döner. Bu, pthread'de bir thread'in segfault ile tüm process'i indirmesinden temelde farklıdır.
Bu bölümde
spawntipli closure alır;void*paketleme yokmoveyakalanan verinin sahipliğini thread'e devrederJoinHandle<T>dönüş değerini tip güvenli taşır;join()bekler- Join edilmeyen thread detach olur; panik o thread'le sınırlı kalır
02 Thread'lerde borrow checker
C'de yığın (stack) üzerindeki bir değişkenin adresini bir thread'e verip ana fonksiyonun erken dönmesi — klasik dangling pointer. Rust bu senaryoyu spawn'ın 'static bound'u ile derleme zamanında reddeder: spawn edilen closure, oluşturan stack frame'den uzun yaşayabilmek zorundadır.
spawn imzası, taşınan closure'ın 'static ömre sahip olmasını ister. Bunun anlamı: closure ödünç alınmış (borrowed) hiçbir referansa bağımlı olamaz; ya sahiplenmiş veri taşır ya da gerçekten 'static olan (örn. statik) bir şeye dokunur. Çıplak bir &veri yakalamak derlenmez.
use std::thread;
fn main() {
let dizi = vec![1, 2, 3];
// HATA: closure `&dizi` ödünç alır, ama thread `dizi`den
// uzun yaşayabilir → derlenmez (E0373/'static ihlali)
let h = thread::spawn(|| {
println!("{:?}", dizi); // borrow
});
h.join().unwrap();
}Derleyicinin önerdiği çözüm tektir: move ile sahipliği taşıyın. Tek bir thread veriyi tüketecekse bu yeterli. Birden fazla thread aynı veriye erişecekse, sahipliği paylaşmak gerekir — bu da Bölüm 05'teki Arc'ın varlık sebebidir.
use std::thread;
fn main() {
let dizi = vec![1, 2, 3];
// move: `dizi`nin sahipliği thread'e geçer; artık borrow yok
let h = thread::spawn(move || {
println!("{:?}", dizi);
});
// burada `dizi` artık kullanılamaz — sahiplik gitti
h.join().unwrap();
}'static burada "programın tüm ömrü kadar yaşar" demek değildir; "ömrü ödünç alınmış bir referansa bağlı değildir" demektir. Sahiplenilmiş bir String ya da Vec mükemmel biçimde 'static'tir, çünkü içsel hiçbir borrow taşımaz.
Bu bölümde
spawnclosure'ın'staticolmasını ister; çıplak referans yakalanamaz- Tek thread tüketecekse
moveile sahipliği taşımak yeterli - Çok thread aynı veriye erişecekse paylaşımlı sahiplik (
Arc) gerekir 'static= "borrow'a bağlı değil", "sonsuza dek yaşar" değil
03 Channel (mpsc)
Paylaşımlı belleği kilitle korumanın alternatifi mesajlaşmadır: "belleği paylaşarak iletişme; ileterek belleği paylaş." std::sync::mpsc, Go'nun kanallarına benzer ama tek farkla — Rust kanalında geçen değerin sahipliği alıcıya transfer olur, kopya değil paylaşım değil, taşıma.
mpsc = multiple producer, single consumer. channel() bir (Sender<T>, Receiver<T>) çifti üretir. Gönderen tarafta send, alan tarafta recv (bloklayan) ya da for v in rx (kanal kapanana dek iterasyon) kullanılır.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel::<String>();
thread::spawn(move || {
let s = String::from("iş sonucu");
tx.send(s).unwrap(); // `s`in SAHİPLİĞİ kanala geçti
// `s` burada artık kullanılamaz — use-after-send imkânsız
});
// recv() bloklar; kanal kapanırsa Err döner
let alinan = rx.recv().unwrap();
println!("ana thread aldı: {}", alinan);
}Sahipliğin transferi, C'de paylaşımlı kuyruklarda görülen en sinsi hatayı kökten yok eder: bir thread bir nesneyi kuyruğa koyup aynı pointer'ı kullanmaya devam etmesi. Rust'ta send'den sonra değer artık gönderende yoktur; data race veya use-after-free yazmak mümkün değildir.
Çoklu üretici
Sender klonlanabilir; her klon aynı tek alıcıya yazar. C'de tek bir kuyruğu N thread'in beslemesi için kuyruğun etrafına manuel mutex sararsınız — burada clone() yeterli, senkronizasyon kanalın içindedir.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
for id in 0..3 {
let tx = tx.clone(); // her üretici kendi Sender'ı
thread::spawn(move || {
tx.send(format!("worker {id}")).unwrap();
});
}
drop(tx); // orijinali bırak; yoksa kanal hiç kapanmaz
// kanal kapanana (tüm Sender düşene) dek iterasyon
for m in rx {
println!("alındı: {}", m);
}
}Klonladıktan sonra orijinal tx'i drop etmeyi unutursanız, for m in rx döngüsü asla bitmez: kanal yalnızca tüm Sender'lar düştüğünde kapanır. Bu, Go'da close(ch)'i unutmaya denk bir tuzaktır.
Bu bölümde
- Kanal = sahiplik transferiyle mesajlaşma; kopya/paylaşım değil taşıma
sendsonrası değer gönderende kalmaz → use-after-send imkânsızSenderklonlanarak çoklu üretici (mpsc) kurulur- Kanal tüm Sender'lar düşünce kapanır; orijinali drop etmeyi unutma
04 Mutex<T>
C'de mutex ile koruduğu veri iki ayrı değişkendir; aralarındaki bağ yalnızca programcının zihnindedir. Rust'ta Mutex<T> veriyi içine alır — korunan değere ancak kilidi alarak ulaşabilirsiniz, dolayısıyla "kilitlemeyi unutmak" tip düzeyinde imkânsızlaşır.
lock() çağrısı bir MutexGuard<T> döndürür. Bu guard, &mut T gibi davranan bir akıllı işaretçidir (Deref + DerefMut); kapsam dışına çıktığında Drop ile kilidi otomatik bırakır. pthread_mutex_unlock'u elle çağırmazsınız — RAII bunu garantiler, erken return ya da panic'te bile.
use std::sync::Mutex;
fn main() {
// Veri kilidin İÇİNDE — ayrı "kilit + veri" hatası yok
let sayac = Mutex::new(0);
{
// lock() == pthread_mutex_lock; guard kilidi temsil eder
let mut g = sayac.lock().unwrap();
*g += 1; // MutexGuard, &mut T gibi davranır
} // g burada drop olur → kilit otomatik bırakılır (unlock)
println!("sayac = {}", *sayac.lock().unwrap());
}Poisoning
Bir thread kilidi tutarken panik atarsa, Mutex poisoned (zehirli) duruma geçer: korunan veri yarım kalmış olabilir. Sonraki lock() çağrıları bunu bir Err ile bildirir; bu yüzden dönüş tipi Result'tır. Sıkça görülen .unwrap() "zehirlenmişse panikle çök" anlamına gelir; gerçek kodda into_inner() ile veriye yine de erişmeyi seçebilirsiniz.
Poisoning C'de yoktur; bir thread kilit altında çökerse C'de mutex bozuk-tutarlı veriyle "açık" kalır ve diğer thread'ler farkında olmadan o veriyi okur. Rust en azından bu durumu Err olarak görünür kılar.
Guard'ı bir değişkene bağlayıp kapsam içinde tutarsanız kilit beklediğinizden uzun süre elde kalır. Kilidin yaşam süresini daraltmak için { ... } bloğu ya da bilinçli bir drop(g) kullanın — aksi halde gizli darboğaz ve deadlock riski.
Bu bölümde
Mutex<T>veriyi içine alır; kilitsiz erişim tipçe imkânsızlock()birMutexGuardverir; drop'ta otomatik unlock (RAII)- Panik altında poisoning kilidi zehirler;
lock()bu yüzdenResult - Guard ömrünü blok/
dropile daralt — uzun tutarsa darboğaz
05 Arc<Mutex<T>>
Tek bir Mutex'i N thread paylaşmak için ona N tane sahip lazım — ama Rust'ta bir değerin tek bir sahibi olur. Çözüm Arc (atomically reference counted): paylaşımlı sahiplik. Thread'ler arası değiştirilebilir paylaşımlı durumun kanonik kalıbı Arc<Mutex<T>>'tir.
Roller nettir: Arc "bu veriye birden çok sahip var" der ve sayım sıfırlanınca veriyi serbest bırakır; Mutex ise "aynı anda yalnız biri yazsın" der. Birlikte: çok sahipli + güvenli değiştirilebilir. Klasik örnek, on thread'in tek bir sayacı artırması.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let sayac = Arc::new(Mutex::new(0));
let mut tutamaclar = vec![];
for _ in 0..10 {
let sayac = Arc::clone(&sayac); // refcount++; veri kopyalanmaz
tutamaclar.push(thread::spawn(move || {
let mut n = sayac.lock().unwrap();
*n += 1; // kilit altında güvenli artış
}));
}
for h in tutamaclar { h.join().unwrap(); }
println!("toplam = {}", *sayac.lock().unwrap()); // 10
}lock() ile erişilir.Neden Rc değil Arc
Rc de paylaşımlı sahiplik verir ama refcount'u atomik değildir — tek thread için tasarlanmıştır, daha hızlıdır. İki thread aynı Rc'yi klonlasa sayaçta data race olurdu. İşte bu yüzden Rc !Send'dir: derleyici onu bir thread'e taşımanıza izin vermez. Arc atomik sayaç kullanır, dolayısıyla Send + Sync'tir. Yani "yanlış sayaç" seçimi derleme hatasıdır, runtime sürprizi değil.
Atomik sayaç ucuz değildir; tek thread'de Rc daima tercih edilir. Arc'ı yalnızca veri gerçekten thread sınırını geçecekse kullanın — derleyici zaten Rc yetersizse uyaracaktır.
Bu bölümde
Arc= paylaşımlı sahiplik;Mutex= güvenli değiştirmeArc::clonesayacı artırır, veriyi kopyalamaz (ucuz)Rcatomik olmayan sayaç yüzünden!Send'dir; thread'e geçemezArc<Mutex<T>>paylaşımlı değiştirilebilir durumun kanonik kalıbı
06 Send ve Sync
Bir tipin thread'lerle nasıl etkileşebileceğini iki auto trait kodlar. Send: tip bir başka thread'e taşınabilir. Sync: &T thread'ler arası paylaşılabilir (eşdeğer olarak: &T Send'dir). C'de "bu struct thread-safe mi?" sorusunun yanıtı belgelemededir; Rust'ta tip sisteminin içindedir.
Bu trait'ler "auto"dur: bileşenlerinin hepsi Send/Sync ise tip otomatik olarak Send/Sync sayılır. Hiçbir şey elle implemente etmezsiniz; derleyici tip ağacını gezerek türetir. Bir tipi kasıtlı olarak unsafe yollarla işaretlemediğiniz sürece kurallar mekaniktir.
| Tip | Send | Sync | Neden |
|---|---|---|---|
Arc<T> | evet* | evet* | atomik refcount (T: Send+Sync ise) |
Rc<T> | hayır | hayır | atomik olmayan sayaç → data race |
Mutex<T> | evet* | evet* | kilit erişimi serileştirir |
Cell/RefCell | evet | hayır | runtime borrow paylaşımda yarışır |
çıplak işaretçi *mut T | hayır | hayır | güvenlik garantisi yok |
Anahtar nokta: thread::spawn'ın bound'u F: Send + 'static'tir. Yani bir thread'e Send olmayan bir şey taşımaya kalkarsanız hata kullanım yerinde, açık bir mesajla çıkar. Rc örneğindeki "neden derlenmedi?" sorusunun tek cümlelik yanıtı: Rc !Send'dir.
use std::rc::Rc;
use std::thread;
fn main() {
let r = Rc::new(5);
// HATA: `Rc<i32>` Send değil → thread'e taşınamaz
// error[E0277]: `Rc<i32>` cannot be sent between threads safely
thread::spawn(move || {
println!("{}", r);
});
}Send ve Sync ortogonaldir. MutexGuard ilginç bir örnektir: Sync'tir (referansı paylaşılabilir) ama bazı platformlarda !Send'dir — yani guard'ı kilitleyen thread bırakmalıdır. Çoğu tipte ikisi birlikte gelir; ayrımı yalnızca bu tür sınır durumlarda hissedersiniz.
Bu bölümde
Send= thread'e taşınabilir;Sync=&Tpaylaşılabilir- Auto trait: bileşenler uygunsa derleyici otomatik türetir
Rc!Send(atomik olmayan sayaç);ArcSend + Syncspawn'ınSend + 'staticbound'u kuralı kullanım yerinde zorlar
07 RwLock ve atomics
Tek bir Mutex okuyucuları da serileştirir; oysa çoğu okuyucu / az yazıcı yükünde bu israftır. RwLock<T> tam olarak pthread_rwlock_t'nin karşılığıdır: aynı anda çok okuyucu ya da tek yazıcı. Daha da hafifi gerekiyorsa, tek skaler değerler için atomics kilidi tamamen ortadan kaldırır.
RwLock iki ayrı kilitleme verir: read() paylaşımlı bir guard (&T), write() dışlayıcı bir guard (&mut T) döndürür. Borrow checker'ın "çok okuyucu veya tek yazıcı" kuralının çalışma zamanına taşınmış halidir.
use std::sync::RwLock;
fn main() {
let ayar = RwLock::new(vec![1, 2, 3]);
{
let r1 = ayar.read().unwrap(); // paylaşımlı; eşzamanlı r2 olabilir
let r2 = ayar.read().unwrap();
println!("{} {}", r1.len(), r2.len());
} // okuyucular drop olur
{
let mut w = ayar.write().unwrap(); // dışlayıcı
w.push(4);
}
}Atomics
Korunan veri tek bir tamsayı ya da bayrak ise kilit aşırıdır. AtomicUsize, AtomicBool gibi tipler donanım atomik komutlarıyla çalışır — kilitsiz (lock-free). C11 <stdatomic.h> ya da GCC __atomic builtin'lerini biliyorsanız model birebir aynıdır; Ordering da C++/C bellek sıralamasının (memory order) aynısıdır.
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
fn main() {
let sayac = Arc::new(AtomicUsize::new(0));
let mut hs = vec![];
for _ in 0..10 {
let c = Arc::clone(&sayac);
hs.push(thread::spawn(move || {
// kilitsiz artış; basit sayaç için Relaxed yeter
c.fetch_add(1, Ordering::Relaxed);
}));
}
for h in hs { h.join().unwrap(); }
println!("{}", sayac.load(Ordering::Relaxed)); // 10
}Ordering seçimi ciddidir. Relaxed yalnızca atomikliği garanti eder, başka veriyle sıralama kurmaz — bağımsız bir sayaç için doğru ama bir bayrakla başka belleği "yayınlıyorsanız" Release/Acquire gerekir. Şüphedeyseniz SeqCst kullanın; C/C++'taki kurallarla aynıdır.
Bu bölümde
RwLock= çok okuyucu / tek yazıcı;pthread_rwlockkarşılığıread()paylaşımlı,write()dışlayıcı guard verir- Tek skaler için
Atomic*kilitsizdir; C11/C++ atomic modeliyle aynı OrderingC/C++ memory order'ıyla birebir; şüphedeSeqCst
08 Scoped threads
spawn'ın 'static bound'u, sadece kısa süre çalışıp ana fonksiyonun yerel verisini ödünç almak isteyen thread'ler için fazla katıdır. std::thread::scope (Rust 1.63+) tam bu boşluğu doldurur: scope bitmeden tüm child thread'lerin join'ini garanti ettiği için, ödünç alınan veriye Arc'sız, klonsuz erişebilirsiniz.
Mekanizma şu: scope kapanmadan önce içinde açılan her thread'i otomatik join eder. Derleyici bunu bildiği için, child thread'lerin yerel bir &data'yı yakalamasına izin verir — çünkü o veri, thread'ler bitmeden asla yok olamaz. 'static şartı bu kapsamda gevşer.
use std::thread;
fn main() {
let dizi = vec![1, 2, 3, 4, 5, 6];
thread::scope(|s| {
// her iki thread `dizi`yi PAYLAŞIMLI ödünç alır — Arc yok
s.spawn(|| {
let toplam: i32 = dizi[..3].iter().sum();
println!("ilk yarı: {toplam}");
});
s.spawn(|| {
let toplam: i32 = dizi[3..].iter().sum();
println!("ikinci yarı: {toplam}");
});
}); // scope kapanır → her iki thread otomatik join edilir
// `dizi` hâlâ geçerli; sahipliği hiç taşınmadı
println!("toplam eleman: {}", dizi.len());
}Scope içinde de &mut kuralları geçerli: aynı veriyi iki thread paylaşımlı (&) okuyabilir, ama ikisi birden değiştiremez. Eşzamanlı yazma gerekiyorsa scope içinde bile Mutex ya da disjoint (örtüşmeyen) dilimler kullanmanız gerekir.
Ne zaman pratik? Veriyi yaşatan stack frame thread'lerden uzun yaşayacaksa ve iş kısa süreliyse scoped threads en temiz seçimdir: Arc/klon gürültüsü ve atomik refcount maliyeti olmadan paralellik. Uzun ömürlü, ana fonksiyondan bağımsız çalışan thread'ler için ise hâlâ klasik spawn + Arc gerekir.
Bu bölümde
thread::scopechild thread'leri scope sonunda otomatik join eder- Bu garanti sayesinde yerel veri
Arc'sız ödünç verilebilir - Paylaşımlı borrow serbest; eşzamanlı yazma için yine
Mutex/disjoint - Kısa ömürlü, yerel veriye dokunan işler için en temiz kalıp
09 Gerçek örnek
Şimdi parçaları birleştirelim: sabit sayıda worker thread'in bir kanaldan iş çekip, sonucu paylaşımlı bir Arc<Mutex<T>>'te biriktirdiği klasik bir desen. Bu, C'de "thread pool + iş kuyruğu + sonuç mutex'i" üçlüsünün doğrudan ama güvenli karşılığıdır.
İşler bir mpsc kanalına gönderilir; N worker aynı Receiver'ı paylaşamayacağı için (mpsc tek tüketicilidir) Receiver'ı Arc<Mutex<...>> ile sararız — her worker kilitleyip bir iş alır. Sonuçlar paylaşımlı bir toplam mutex'inde birikir.
use std::sync::{Arc, Mutex, mpsc};
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel::<u64>();
// rx paylaşımlı: çok worker tek kuyruktan çeker
let rx = Arc::new(Mutex::new(rx));
let toplam = Arc::new(Mutex::new(0u64));
let mut workerlar = vec![];
for _ in 0..4 {
let rx = Arc::clone(&rx);
let toplam = Arc::clone(&toplam);
workerlar.push(thread::spawn(move || {
loop {
// kilidi kısa tut: yalnız bir iş çek, sonra bırak
let is = rx.lock().unwrap().recv();
match is {
Ok(n) => {
let kare = n * n; // kilit DIŞINDA hesapla
*toplam.lock().unwrap() += kare;
}
Err(_) => break, // kanal kapandı → çık
}
}
}));
}
for i in 1..=100u64 { tx.send(i).unwrap(); }
drop(tx); // tüm işler gönderildi; kanalı kapat
for w in workerlar { w.join().unwrap(); }
println!("kareler toplamı = {}", *toplam.lock().unwrap()); // 338350
}Dikkat edilen iki nokta üretim kodunda kritiktir: ağır hesaplama (n * n burada simgesel) kilit dışında yapılır, böylece worker'lar darboğaza girmez; ve kanal drop(tx) ile kapatıldığı için recv() bir Err döner ve döngü temiz biter. C'de bu "kapanış sinyali" için ayrı bir bayrak + condition variable kurardınız.
Kapanış: rayon
Yukarıdaki desen heterojen iş kuyrukları için doğru araçtır. Ama yaptığınız tek şey bir koleksiyonu paralel işlemekse (data parallelism), elle thread pool kurmak gereksizdir. rayon crate'i bunu tek satıra indirir: iter() yerine par_iter() yazarsınız, kütüphane work-stealing scheduler ile çekirdeklere böler.
use rayon::prelude::*;
fn main() {
// aynı toplam, sıfır manuel senkronizasyon
let toplam: u64 = (1..=100u64)
.into_par_iter() // paralel iterator
.map(|n| n * n)
.sum();
println!("kareler toplamı = {toplam}"); // 338350
}rayon güvenliğini yine Send/Sync'ten alır: par_iter, closure'ınızın Send olmasını ister, yani aynı derleme zamanı veri-yarışı garantisi geçerlidir. Pratik kural: düzenli data parallelism → rayon; özel iş akışı / heterojen görevler → elle channel + Arc<Mutex>.
Bu bölümde
- Worker pool:
Arc<Mutex<Receiver>>ile çok worker tek kuyruktan çeker - Ağır işi kilit dışında yap; kilidi sadece veri paylaşımı için kısa tut
drop(tx)kanalı kapatır → worker'larErrgörüp temiz biter- Düz data parallelism için
rayonpar_iterelle pool kurmaya yeğdir