Boxile bir değeri öbekte saklamak ve özyinelemeli bir tür kurmakRcile birden çok sahip,RefCellile iç değiştirilebilirlik oluşturmak- İş parçacıklarını
moveclosure'larıyla başlatmak ve sonuçlarıjoinile toplamak - Kanallar ve
Arc<Mutex<T>>ile iş parçacıkları arasında veri paylaşmak
Şimdiye kadar her değerin tek bir sahibi vardı. Ama bazen başka şeyler gerekir: boyutu önceden bilinmeyen özyinelemeli bir yapı, aynı şarkının birkaç çalma listesinde olması, ortak veriyi değiştirmek ya da işi işlemcinin birkaç çekirdeğine bölmek. Bunun için Rust'ta akıllı işaretçiler ve eşzamanlılık (concurrency) araçları vardır; onlar da sahiplik kurallarına uyar.
`Box<T>`: öbekte bir değer
Box::new(x) bir değeri öbeğe yerleştirir; yığında yalnızca sabit boyutlu bir işaretçi kalır. Kutunun sahibi kapsam dışına çıkınca öbekteki değer de otomatik olarak düşürülür. Box üç durumda gerekir: boyutu derleme sırasında bilinmeyen özyinelemeli türler, Box<dyn Trait> gibi trait nesneleri ve yığında taşınması pahalı olan büyük veriler.
enum List {
Node(i32, List),
Empty,
}enum List {
Node(i32, Box<List>),
Empty,
}List kendi içinde bir List barındırıyor; bu yüzden derleyici şunu yazar: recursive type List has infinite size. Box ise sabit boyutlu bir işaretçidir ve döngüyü kırar.enum List {
Node(i32, Box<List>),
Empty,
}
use List::{Empty, Node};
fn sum(list: &List) -> i32 {
match list {
Node(value, next) => value + sum(next),
Empty => 0,
}
}
fn main() {
let boxed = Box::new(5);
println!("boxed + 1 = {}", *boxed + 1);
let list = Node(1, Box::new(Node(2, Box::new(Node(3, Box::new(Empty))))));
println!("sum = {}", sum(&list));
}boxed + 1 = 6 sum = 6
`Rc<T>`: birden çok sahip
Rc (reference counting, referans sayma) bir değerin birden çok sahibi olmasını sağlar. Rc::clone(&x) veriyi kopyalamaz, yalnızca bir sayacı bir artırır; bu çok ucuzdur. Her sahip düşürüldüğünde sayaç azalır, sıfıra ulaşınca değer düşürülür. Rc değeri yalnızca okumaya izin verir ve yalnızca tek bir iş parçacığı içinde çalışır.
use std::rc::Rc;
fn main() {
let song = Rc::new(String::from("lesson.mp3"));
println!("owners: {}", Rc::strong_count(&song));
let playlist_a = Rc::clone(&song);
{
let playlist_b = Rc::clone(&song);
println!("owners: {}", Rc::strong_count(&song));
println!("b plays {playlist_b}");
}
println!("owners: {}", Rc::strong_count(&song));
println!("a plays {playlist_a}");
}owners: 1 owners: 3 b plays lesson.mp3 owners: 2 a plays lesson.mp3
playlist_b iç blok bitince düşürüldü ve sayaç 3'ten 2'ye indi. Metnin kendisi bellekte yalnızca bir kez saklanır.`RefCell<T>`: iç değiştirilebilirlik
RefCell, ödünç alma kurallarını derleme sırasında değil, program çalışırken denetler. borrow() okumak için, borrow_mut() değiştirmek için ödünç verir. Bu, değiştirilemez bir referansın arkasındaki değeri değiştirmeyi mümkün kılar. Rc<RefCell<T>> birleşimi tipik bir çözümdür: birkaç sahip ortak veriyi hem okuyabilir hem de değiştirebilir.
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let scores = Rc::new(RefCell::new(vec![80, 95]));
let teacher = Rc::clone(&scores);
teacher.borrow_mut().push(70);
scores.borrow_mut().push(100);
println!("{:?}", scores.borrow());
let total: i32 = teacher.borrow().iter().sum();
println!("total = {total}");
}[80, 95, 70, 100] total = 345
| Tür | Sahipler | Değiştirme | İş parçacıkları arası paylaşım |
|---|---|---|---|
Box<T> | bir | sahip mut ise | evet |
Rc<T> | birden çok | hayır | hayır |
RefCell<T> | bir | evet, çalışma sırasında denetlenir | hayır — başka bir iş parçacığına taşınabilir ama paylaşılamaz (Sync değildir) |
Arc<T> | birden çok | hayır | evet |
Arc<Mutex<T>> | birden çok | evet, kilit aracılığıyla | evet |
İş parçacıkları
thread::spawn, bir closure'ı yeni bir iş parçacığında çalıştırır ve bir JoinHandle döndürür. join(), iş parçacığının bitmesini bekler ve sonucunu verir. Closure'ın önündeki move, kullandığı değişkenlerin sahipliğini iş parçacığına devreder. Dikkat: iş parçacıklarının kendi içinden yazdırdığı satırların sırası her çalıştırmada farklı olabilir. Bu yüzden aşağıda iş parçacıkları sonuç döndürüyor, main ise onları sabit bir sırayla yazdırıyor.
use std::thread;
fn main() {
let mut handles = Vec::new();
for id in 1..=3_u64 {
let handle = thread::spawn(move || {
let sum: u64 = (1..=id * 1000).sum();
(id, sum)
});
handles.push(handle);
}
for handle in handles {
let (id, sum) = handle.join().unwrap();
println!("thread {id}: sum = {sum}");
}
}thread 1: sum = 500500 thread 2: sum = 2001000 thread 3: sum = 4501500
use std::thread;
fn main() {
let names = vec!["Aysel", "Murad"];
let handle = thread::spawn(|| {
println!("{:?}", names);
});
handle.join().unwrap();
}use std::thread;
fn main() {
let names = vec!["Aysel", "Murad"];
let handle = thread::spawn(move || {
println!("{:?}", names);
});
handle.join().unwrap();
}closure may outlive the current function, but it borrows names. İş parçacığı main'den uzun yaşayabilir; bu yüzden bir referans yetmez. Sağdaki program ["Aysel", "Murad"] yazdırır.Kanallar ve `Arc<Mutex<T>>`
İş parçacıkları arasında veri paylaşmanın iki yolu vardır. Birincisi kanaldır: mpsc::channel() bir gönderici (tx) ve bir alıcı (rx) oluşturur; bir iş parçacığı send ile gönderir, diğeri alır. Alıcı for ile dolaşılabilir; tüm göndericiler düşürülünce döngü biter. Tek bir göndericinin mesajları gönderildiği sırayla gelir.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
let worker = thread::spawn(move || {
for step in ["download", "unpack", "install"] {
tx.send(step).unwrap();
}
});
for message in rx {
println!("got: {message}");
}
worker.join().unwrap();
println!("done");
}got: download got: unpack got: install done
İkinci yol ortak durumdur. Arc, Rc'nin iş parçacıkları için güvenli (atomik) sürümüdür; Mutex ise bir kilittir: lock() çağıran iş parçacığı veriye tek başına erişir, döndürülen koruyucu (guard) kapsam dışına çıkınca kilit otomatik olarak açılır. Birlikte Arc<Mutex<T>>, birkaç iş parçacığının aynı değeri sırayla değiştirmesini sağlar.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = Vec::new();
for _ in 0..10 {
let counter = Arc::clone(&counter);
handles.push(thread::spawn(move || {
let mut n = counter.lock().unwrap();
*n += 1;
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}Result: 10
Arc'ın kendi kopyasını alır; bu, gölgelemeyle yazılmıştır: let counter = Arc::clone(&counter);.Önemli noktalar
Box<T>bir değeri öbekte tutar; özyinelemeli türler vedyn Traitiçin gereklidir.Rc<T>tek bir iş parçacığında birden çok sahip sağlar;Rc::cloneyalnızca sayacı artırır.RefCell<T>ödünç kurallarını çalışma sırasında denetler; ihlal paniğe yol açar.thread::spawn(move || ...)bir iş parçacığı başlatır,join()sonucunu bekler; yazdırmayı sabit sıraylamainiçinden yap.- Kanallar mesaj iletir,
Arc<Mutex<T>>ise iş parçacıkları arasında ortak ve değiştirilebilir durum sağlar.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.
enum List { Node(i32, List), Empty } neden derlenmez?