[[📚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トレイト]]を実装している
- 複数のスレッドでアクセスを共有するのに使用できる