Reputation: 45
Is there any way to share data between threads (with Arc
) but only allow a single thread to be able to mutate that data?
Something like that would be possible in C but I can't see how to do this in Rust.
Arc<Mutex<T>>
allows all threads to mutate, while Arc<T>
allows none.
Upvotes: 1
Views: 997
Reputation: 27905
You can use the type system to wrap an Arc<Mutex<T>>
in a way that disallows mutation except by one privileged owner. Here's an example:
use std::sync::Arc;
use std::sync::Mutex;
pub struct Writer<T>(Arc<Mutex<T>>);
impl<T> Writer<T> {
pub fn new(value: T) -> Self {
Writer(Arc::new(Mutex::new(value)))
}
pub fn reader(&self) -> Reader<T> {
Reader(Arc::clone(&self.0))
}
pub fn set(&self, value: T) {
*self.0.lock().unwrap() = value;
}
pub fn get(&self) -> T
where
T: Clone,
{
self.0.lock().unwrap().clone()
}
}
pub struct Reader<T>(Arc<Mutex<T>>);
// derive(Clone) uses incorrect bounds, so we must implement Clone manually
// (see https://stackoverflow.com/q/39415052/3650362)
impl<T> Clone for Reader<T> {
fn clone(&self) -> Self {
Reader(Arc::clone(&self.0))
}
}
impl<T> Reader<T> {
pub fn get(&self) -> T
where
T: Clone,
{
self.0.lock().unwrap().clone()
}
}
If you put this code in a mod
ule, Rust's privacy controls will prove that no user can duplicate a Writer
or turn a Reader
into a Writer
except through the use of unsafe
. Therefore you can clone and send Reader
s to as many threads as you like, but send the Writer
only to the particular thread that should have write access.
There are many possible variations on this design; for instance, you could use RwLock
instead of Mutex
to let multiple readers access the value simultaneously while it's not being written to.
Playground (based on Akiner Alkan's example)
Something like that would be possible in say C
Note that just as in Rust, if you want to do this safely in C, you need some kind of synchronization (a mutex or similar). Rust insists that you be explicit about how to avoid data races. C is different in that it will just assume you know what you're doing and then punish you savagely for writing races. In Rust the idiomatic approach is to use the safe abstractions provided by the standard library. However, if you have some other means of synchronization and can prove that Mutex
is unnecessary overhead, you can always just write things in the C way -- raw pointers are essentially the same in both Rust (within an unsafe
block) and C.
Upvotes: 2
Reputation: 6882
You can create a wrapper around Arc<Mutex<T>>
and set the values via setter method with the key which is provided by the creator of the arc.
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
#[derive(Clone)]
pub struct CustomArc<T> {
mutable_arc: Arc<Mutex<T>>,
mutator_key: String,
}
#[derive(Clone)]
struct MyStruct {
inner_val: i32,
}
impl MyStruct {
fn set_val(&mut self, val: i32) {
self.inner_val = val;
}
fn get_val(&mut self) -> i32 {
self.inner_val.clone()
}
}
impl CustomArc<MyStruct> {
fn new(val: MyStruct, mutator_key: String) -> CustomArc<MyStruct> {
CustomArc {
mutable_arc: Arc::new(Mutex::new(val)),
mutator_key,
}
}
fn set_inner_val(&mut self, value: i32, mutator_key: String) -> Result<(), SetError> {
if mutator_key == self.mutator_key {
self.mutable_arc.lock().unwrap().set_val(value);
return Ok(());
}
Err(SetError::CouldNotSet)
}
fn get_inner_val(&self) -> i32 {
self.mutable_arc.lock().unwrap().get_val()
}
}
enum SetError {
CouldNotSet,
}
fn main() {
let my_struct = MyStruct { inner_val: 3 };
let custom_arc = CustomArc::new(my_struct, "OwnerKey".to_string());
let mut custom_arc1 = custom_arc.clone();
let mut custom_arc2 = custom_arc.clone();
let mut custom_arc3 = custom_arc.clone();
thread::spawn(move || {
println!(
"Thread1 -> Current Value: {:?}",
custom_arc1.get_inner_val()
);
if let Err(_err) = custom_arc1.set_inner_val(4, "AnotherKey".to_string()) {
println!("Could not write in thread1");
}
println!("Thread1 -> Value: {:?}", custom_arc1.get_inner_val());
});
thread::sleep_ms(500);
thread::spawn(move || {
println!(
"Thread2 -> Current Value: {:?}",
custom_arc2.get_inner_val()
);
if let Err(_err) = custom_arc2.set_inner_val(5, "OwnerKey".to_string()) {
println!("Could not write in thread2");
}
println!("Thread2 -> Value: {:?}", custom_arc2.get_inner_val());
});
thread::sleep_ms(500);
thread::spawn(move || {
println!(
"Thread3 -> Current Value: {:?}",
custom_arc3.get_inner_val()
);
if let Err(_err) = custom_arc3.set_inner_val(6, "SomeKey".to_string()) {
println!("Could not write in thread3");
}
println!("Thread3 -> Value: {:?}", custom_arc3.get_inner_val());
});
thread::sleep_ms(500);
}
Since your CustomArc
is public and the mutable_arc
field is private, you should access them via setter and getters from outside of the crate. The owner(possible another threads) of the mutator_key
has right to mutate the inner data.
Upvotes: 0