Rc<RefCell<T>> / Arc<Mutex<T>>
| 対応: | Rust 1.0(2015) |
|---|
『Rc<RefCell<T>>』はシングルスレッドで複数所有+内部可変性を実現するパターンで、『Arc<Mutex<T>>』はマルチスレッドでスレッドセーフな複数所有+内部可変性を実現するパターンです。
構文
use std::rc::Rc;
use std::cell::RefCell;
use std::sync::{Arc, Mutex};
// シングルスレッド:複数所有 + 内部可変性
let shared = Rc::new(RefCell::new(値));
let clone = Rc::clone(&shared);
*shared.borrow_mut() = 新しい値;
// マルチスレッド:スレッドセーフな複数所有 + 内部可変性
let shared = Arc::new(Mutex::new(値));
let clone = Arc::clone(&shared);
let mut guard = shared.lock().unwrap();
*guard = 新しい値;
組み合わせ一覧
| パターン | スレッド | 概要 |
|---|---|---|
| Rc<RefCell<T>> | シングルのみ | 参照カウント共有 + 実行時借用チェックによる可変性です。 |
| Arc<Mutex<T>> | マルチ対応 | アトミック参照カウント共有 + Mutexによる排他制御です。 |
| Arc<RwLock<T>> | マルチ対応 | 読み取り専用ロックを複数スレッドが同時に取得できます。 |
サンプルコード
sample_rc_refcell_arc_mutex.rs
use std::rc::Rc;
use std::cell::RefCell;
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Rc<RefCell<T>>:シングルスレッドで共有リストに追加します。
let list = Rc::new(RefCell::new(vec![]));
let list_a = Rc::clone(&list);
let list_b = Rc::clone(&list);
list_a.borrow_mut().push(1);
list_b.borrow_mut().push(2);
list.borrow_mut().push(3);
println!("{:?}", list.borrow()); // [1, 2, 3]
// Arc<Mutex<T>>:マルチスレッドでカウンタを共有します。
let counter = Arc::new(Mutex::new(0));
let handles: Vec<_> = (0..5).map(|_| {
let cnt = Arc::clone(&counter);
thread::spawn(move || {
let mut n = cnt.lock().unwrap();
*n += 1; // Mutexでロックを取ってから変更します。
})
}).collect();
for h in handles {
h.join().unwrap();
}
println!("counter: {}", *counter.lock().unwrap()); // counter: 5
}
rustc rc_refcell_arc_mutex.rs ./rc_refcell_arc_mutex [1, 2, 3] counter: 5
rc_refcell_arc_mutex2.rs — Arc<RwLock<T>>:読み取り優先の共有
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let config = Arc::new(RwLock::new(vec!["Kogami Shinya", "Tsunemori Akane"]));
// 複数スレッドが同時に読み取り可能です。
let readers: Vec<_> = (0..3).map(|i| {
let cfg = Arc::clone(&config);
thread::spawn(move || {
let data = cfg.read().unwrap(); // 読み取りロックを取得します。
println!("reader {}: {:?}", i, *data);
})
}).collect();
for h in readers {
h.join().unwrap();
}
// 書き込みは排他ロックです。
{
let mut data = config.write().unwrap();
data.push("Ginoza Nobuchika");
println!("書き込み完了: {:?}", *data);
}
}
rustc rc_refcell_arc_mutex2.rs ./rc_refcell_arc_mutex2 reader 0: ["Kogami Shinya", "Tsunemori Akane"] reader 1: ["Kogami Shinya", "Tsunemori Akane"] reader 2: ["Kogami Shinya", "Tsunemori Akane"] 書き込み完了: ["Kogami Shinya", "Tsunemori Akane", "Ginoza Nobuchika"]
rc_refcell_arc_mutex3.rs — Rc<RefCell<T>>:グラフ構造の構築
use std::rc::Rc;
use std::cell::RefCell;
type NodeRef = Rc<RefCell<Node>>;
struct Node {
value: i32,
children: Vec<NodeRef>,
}
impl Node {
fn new(value: i32) -> NodeRef {
Rc::new(RefCell::new(Node { value, children: vec![] }))
}
fn add_child(parent: &NodeRef, child: NodeRef) {
parent.borrow_mut().children.push(child);
}
}
fn print_tree(node: &NodeRef, depth: usize) {
println!("{}{}", " ".repeat(depth), node.borrow().value);
for child in &node.borrow().children {
print_tree(child, depth + 1);
}
}
fn main() {
let root = Node::new(1);
let left = Node::new(2);
let right = Node::new(3);
let leaf = Node::new(4);
Node::add_child(&left, Rc::clone(&leaf));
Node::add_child(&root, Rc::clone(&left));
Node::add_child(&root, Rc::clone(&right));
print_tree(&root, 0);
}
rustc rc_refcell_arc_mutex3.rs
./rc_refcell_arc_mutex3
1
2
4
3
概要
Rustでは「複数の所有者から書き換えたい」という需要を単一のスマートポインタでは満たせないため、複数のスマートポインタを組み合わせるパターンがよく使われます。例えば『Rc<T>』で共有している値を変更したい場合、通常の『&mut』は使えません。このような場面で『RefCell<T>』による内部可変性が必要になります。シングルスレッドなら『Rc<RefCell<T>>』、マルチスレッドなら『Arc<Mutex<T>>』が定番の組み合わせです。
Mutexはロックの解放忘れを防ぐため、Rustではガード(MutexGuard)がスコープを抜けると自動的にロックが解放される設計になっています。
複数のMutexを異なる順序でロックするとデッドロックが発生します。複数のロックが必要な場合は、常に同じ順序でロックを取得するよう設計してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。