RustにおけるShared-State Concurrency(共有状態の並行性)とその拡張であるSendとSyncの概念について、完全かつ包括的な記事をお届けします。このトピックは、Rustの並行性を理解するために非常に重要な要素です。Rustの並行性の特長を活かし、安全で効率的なプログラミングを行うために必要な知識を提供します。
1. Rustにおける並行性の基礎
Rustは、並行プログラミングを行う際に非常に強力でありながら、安全性を重視する言語です。Rustの並行性の最も特徴的な点は、「データ競合(data race)」をコンパイル時に防ぐことができる点です。Rustでは、並行性を扱うために、主に**共有状態(shared state)とメッセージパッシング(message passing)**の二つの方法があります。

このうち、共有状態による並行性は、複数のスレッドが同じデータにアクセスする場合に重要です。これを実現するために、Rustは所有権(ownership)と借用(borrowing)という概念を用いて、データの安全なアクセスを保証します。しかし、スレッド間でデータを共有する際に、同時に書き込みが行われるとデータ競合が発生する可能性があります。この問題に対処するために、RustはSendとSyncというトレイト(trait)を導入しています。
2. Sendトレイト
Sendは、Rustの型システムにおけるトレイトで、ある型がスレッド間で安全に送信可能であることを示します。具体的には、Sendトレイトを実装している型は、スレッド間で所有権を移動することができるため、別のスレッドでそのデータを使用することができます。
Sendの特徴
- Sendが実装されている型は、その型の値を他のスレッドに安全に渡すことができます。
- 例えば、
Vec
やBox
など、データを所有する型が Send を実装しています。 - 逆に、スレッド間で安全に送信できない型(例:
Rc
やRefCell
)は Send を実装しません。
Rustのコンパイラは、型が Send を実装しているかどうかをチェックし、もし安全でない方法でスレッド間のデータを移動しようとした場合、コンパイルエラーを発生させます。
rustuse std::thread;
fn main() {
let s = String::from("Hello, Rust!");
let handle = thread::spawn(move || {
println!("{}", s); // 所有権が移動しているので、`s` は `move` されている
});
handle.join().unwrap();
}
上記のコードでは、s
は move
によって新しいスレッドに所有権が移されます。このため、String
型は Send を実装しており、スレッド間で安全にデータを移動できることが保証されています。
3. Syncトレイト
Syncは、Rustの型システムにおけるもう一つの重要なトレイトで、ある型が複数のスレッドから同時に参照されても安全であることを示します。具体的には、Syncトレイトを実装している型は、複数のスレッドから並行してアクセスされてもデータ競合が発生しないように設計されています。
Syncの特徴
- Syncが実装されている型は、複数のスレッドが同時に参照することができます。
- 例えば、
&T
(参照)は、T
が Sync を実装している場合、複数のスレッドから安全に参照できます。 - 一方、
RefCell
やRc
などの型は、Sync
を実装していません。これらの型は、内部に可変状態を持っており、複数のスレッドから同時に参照されるとデータ競合が発生する可能性があります。
rustuse std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let data = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let data_clone = Arc::clone(&data);
let handle = thread::spawn(move || {
let mut num = data_clone.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *data.lock().unwrap());
}
この例では、Arc
(スレッド安全な参照カウント付きポインタ)とMutex
(ミューテックス)を使用して、複数のスレッドから安全にデータにアクセスしています。Arc
はSyncを実装しており、Mutex
もスレッド間で安全にデータをロックするために使われます。これにより、複数のスレッドが同時にデータを操作してもデータ競合が防止されます。
4. SendとSyncの違い
- Send: スレッド間でデータの所有権を安全に移動できるかどうかを示します。型が Send を実装していれば、その型の値を他のスレッドに移動することができます。
- Sync: 複数のスレッドが同時にデータにアクセスしても安全かどうかを示します。型が Sync を実装していれば、その型のデータを複数のスレッドで参照することができます。
これらのトレイトは、Rustにおける並行性の安全性を保証するための重要な役割を果たしています。Rustの型システムと所有権のメカニズムにより、データ競合を防ぎ、安全な並行プログラミングが可能になります。
5. まとめ
Rustの並行性は非常に強力で、SendとSyncのトレイトを活用することで、安全にスレッド間でデータをやりとりすることができます。Sendはデータの所有権をスレッド間で移動可能にし、Syncはデータを複数のスレッドから同時に参照可能にします。これにより、データ競合を防ぎ、並行プログラミングを安全かつ効率的に実現できます。
Rustの並行性におけるこれらのトレイトの理解は、スレッド安全なコードを作成するための鍵です。Rustを使う際には、これらのトレイトを適切に使用し、安全な並行プログラミングを行いましょう。