Nikos Kostoulas
Nikos Kostoulas

Reputation: 45

Is it possible to allow only exactly one thread to mutate shared data?

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

Answers (2)

trent
trent

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 module, 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 Readers 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

Akiner Alkan
Akiner Alkan

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);
}

Playground

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

Related Questions