[[📚The Rust Programming Language|the book]]の第16章。 ## スレッドを使用してコードを同時に走らせる - [[Rust]]は標準ライブラリでは、1:1スレッド(OSのスレッド)のみをサポート - [[グリーンスレッド]] - ランタイム - ここでは **言語によってすべてのバイナリに含まれるコードのこと** を指す - ランタイムが大きいとバイナリサイズも大きい (同梱されているしな...) - [[Rust]]はほぼゼロの[[ランタイム]] - そのため[[グリーンスレッド]]はサポートしていない - `let handler = thread::spawn(クロージャー)`でスレッドの生成と起動 - `handler.join().unwrap()`でスレッドブロック ```rust use std::thread; use std::time::Duration; fn main() { let handler = thread::spawn(|| { for i in 1..10 { println!("Hello, {}", i); thread::sleep(Duration::from_millis(1)); } }); handler.join().unwrap(); for i in 1..5 { println!("Main thread {}", i); thread::sleep(Duration::from_millis(1)); } } ``` [[moveクロージャ]]再び。 ## メッセージ受け渡しを使ってスレッド間でデータを転送する - [[メモリを共有することでやり取りするな; 代わりにやり取りすることでメモリを共有しろ]] - [[チャンネル]] - mpsc (multiple producer, single consumer) - 送信側は複数、受信側は1つ - `rx.recv`はブロック待機 ```rust use std::sync::mpsc; use std::thread; use std::time::Duration; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from("hi"); // 送るまで3秒待機 thread::sleep(Duration::from_secs(3)); tx.send(val).unwrap(); }); // recvはメインスレッドをブロックして待機する let received = rx.recv().unwrap(); eprintln!("Got: {:?}", received); } ``` sendの時点で[[所有権]]は[[ムーブ]]している。これは並列プラグラミングの不具合を防ぐ意味でもかなり有意義。 ```rust use std::sync::mpsc; use std::thread; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from("hi"); tx.send(val).unwrap(); println!("val is {}", val); // Borrow of moved value: `val` }); let received = rx.recv().unwrap(); println!("Got: {}", received); } ``` for文によるイテレートで[[チャンネル]]が閉じるまでの間、ループで処理できる。`recv`を明示的に呼ぶ必要がない。 ```rust use std::sync::mpsc; use std::thread; use std::time::Duration; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let vals = vec![ String::from("hi"), String::from("from"), String::from("the"), String::from("thread"), ]; for val in vals { tx.send(val).unwrap(); thread::sleep(Duration::from_secs(1)); } }); for received in rx { println!("Got: {}", received); } } ``` `mpsc::Sender::clone(&tx)`でSenderのクローンができる。 ## 状態共有並行性 - 複数のスレッドが同時に同じメモリ位置にアクセスできる - [[Mutex]] ```rust use std::sync::Mutex; fn main() { // Mutex<i32> let m = Mutex::new(5); { // MutexGuard<i32> let mut num = m.lock().unwrap(); // MutexGuard<i32>はi32への可変参照として利用できる *num = 6; } eprintln!("m = {:?}", m); } ``` `*MutextGuard<i32>`が`i32`となるのは以下の[[DerefMutトレイト]]の実装によるもの。 ```rust #[stable(feature = "rust1", since = "1.0.0")] impl<T: ?Sized> DerefMut for MutexGuard<'_, T> { fn deref_mut(&mut self) -> &mut T { unsafe { &mut *self.lock.data.get() } } } ``` [[Dropトレイト]]の実装もあるのか...見落としてた。`poison`? ```rust #[stable(feature = "rust1", since = "1.0.0")] impl<T: ?Sized> Drop for MutexGuard<'_, T> { #[inline] fn drop(&mut self) { unsafe { self.lock.poison.done(&self.poison); self.lock.inner.raw_unlock(); } } } ``` [[スコープ (Rust)|スコープ]]を外れたときに自動でロックが外れるので安心...と。 ```rust for _ in 0..10 { let handle = thread::spawn(move || { // counterで所有権エラーが起こるのはループ処理でcounterが複数回使われるからか... let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } ``` [[Rc]]を使って複数の参照を扱ってみる。 ```rust use std::rc::Rc; use std::sync::Mutex; use std::thread; fn main() { let counter = Rc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Rc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } // `std::rc::Rc<std::sync::Mutex<i32>>` cannot be sent between threads safely [E0277] for handle in handles { handle.join().unwrap(); } println!("Result: {}", *counter.lock().unwrap()) } ``` [[Rc]]はスレッド間で安全に使えないみたい。シングルスレッドでって説明にもあるしな...。 - ここで[[Arc]]が登場 - 標準ライブラリが基本は[[Rc]]で[[Arc]]を使っていないのはパフォーマンスによる - パフォーマンスを犠牲して得られるもの - 本当に必要でなければ使わない方がいい - **[[Rc]]を[[Arc]]に置き換えるだけで動く** ## SyncとSendトレイトで拡張可能な並行性 - [[Sendトレイト]] - [[Sendトレイト]]を実装した型の[[所有権]]は、スレッド間で転送できる - [[Rc]]はcloneしてからスレッド間で転送すると、それぞれのスレッドで参照カウントを更新できてしまうため、スレッド間転送はNG - だから[[Sendトレイト]]は実装されていない - [[Syncトレイト]] - [[Sendトレイト]]と異なり、複数のスレッドから参照されても安全 - `&T`が[[Sendトレイト]]なら、型`T`は[[Syncトレイト]] - [[Rc]]は[[Syncトレイト]]を実装していない - [[Cell]]も[[Syncトレイト]]を実装していない - [[Mutex]]は[[Syncトレイト]]を実装している - 複数のスレッドでアクセスを共有するのに使用できる