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>> | マルチ対応 | 読み取り専用ロックを複数スレッドが同時に取得できます。 |
サンプルコード
『Rc』と『RefCell』を組み合わせた内部可変性、および『Arc』と『Mutex』の使い方を確認するサンプルコードです。
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
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
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
よくあるミス
よくあるミス1: RefCellのborrow_mutを二重に取ってpanic
同じスコープで複数のborrow_mut()を取得しようとすると実行時panicになります。コンパイルエラーにはならないため注意が必要です。
rc_refcell_mistake1.rs
use std::cell::RefCell;
fn main() {
let data = RefCell::new(vec![1, 2, 3]);
let mut v1 = data.borrow_mut();
// 既にborrow_mutを保持中にもう一度borrow_mutを呼びます。
let mut v2 = data.borrow_mut(); // 実行時panicになります。
v1.push(4);
v2.push(5);
}
rustc rc_refcell_mistake1.rs ./rc_refcell_mistake1 thread 'main' panicked at 'already mutably borrowed: BorrowMutError'
borrow_mutのスコープを分けるか、try_borrow_mut()を使います。
rc_refcell_fix1.rs
use std::cell::RefCell;
fn main() {
let data = RefCell::new(vec![1, 2, 3]);
{
let mut v = data.borrow_mut();
v.push(4);
} // ここでdropされます。
{
let mut v = data.borrow_mut();
v.push(5);
}
println!("{:?}", data.borrow()); // [1, 2, 3, 4, 5]
}
rustc rc_refcell_fix1.rs ./rc_refcell_fix1 [1, 2, 3, 4, 5]
よくあるミス2: 複数Mutexの順序不統一でデッドロック
2つのMutexを異なる順序でロックすると、スレッドAがlock1を取ってlock2を待ち、スレッドBがlock2を取ってlock1を待つというデッドロックが発生する可能性があります。
rc_refcell_mutex_note.rs
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let lock1 = Arc::new(Mutex::new(0));
let lock2 = Arc::new(Mutex::new(0));
// 全スレッドで同じ順序(lock1→lock2)でロックを取ることでデッドロックを防ぎます。
let l1 = Arc::clone(&lock1);
let l2 = Arc::clone(&lock2);
let h1 = thread::spawn(move || {
let _a = l1.lock().unwrap(); // 先にlock1を取ります。
let _b = l2.lock().unwrap(); // 次にlock2を取ります。
println!("thread 1: locked");
});
let l1 = Arc::clone(&lock1);
let l2 = Arc::clone(&lock2);
let h2 = thread::spawn(move || {
let _a = l1.lock().unwrap(); // こちらも先にlock1を取ります。
let _b = l2.lock().unwrap();
println!("thread 2: locked");
});
h1.join().unwrap();
h2.join().unwrap();
}
rustc rc_refcell_mutex_note.rs ./rc_refcell_mutex_note thread 1: locked thread 2: locked
概要
Rustでは「複数の所有者から書き換えたい」という需要を単一のスマートポインタでは満たせないため、複数のスマートポインタを組み合わせるパターンがよく使われます。例えば『Rc<T>』で共有している値を変更したい場合、通常の『&mut』は使えません。このような場面で『RefCell<T>』による内部可変性が必要になります。シングルスレッドなら『Rc<RefCell<T>>』、マルチスレッドなら『Arc<Mutex<T>>』が定番の組み合わせです。
Mutexはロックの解放忘れを防ぐため、Rustではガード(MutexGuard)がスコープを抜けると自動的にロックが解放される設計になっています。
複数のMutexを異なる順序でロックするとデッドロックが発生します。複数のロックが必要な場合は、常に同じ順序でロックを取得する必要があります。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。