Caution
お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。
Rc<RefCell<T>> / Arc<Mutex<T>>
『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>> | マルチ対応 | 読み取り専用ロックを複数スレッドが同時に取得できます。 |
サンプルコード
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
}
概要
Rustでは「複数の所有者から書き換えたい」という需要を単一のスマートポインタでは満たせないため、複数のスマートポインタを組み合わせるパターンがよく使われます。シングルスレッドなら『Rc<RefCell<T>>』、マルチスレッドなら『Arc<Mutex<T>>』が定番の組み合わせです。
Mutexはロックの解放忘れを防ぐため、Rustではガード(MutexGuard)がスコープを抜けると自動的にロックが解放される設計になっています。
複数のMutexを異なる順序でロックするとデッドロックが発生します。複数のロックが必要な場合は、常に同じ順序でロックを取得するよう設計してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。