10

I'm trying to send a "view" of a read-only data to another thread for processing. Basically the main thread does work, and continuously updates a set of data. Whenever an update occurs, the main thread should send the updated data down to other threads where they will process it in a read-only manner. I do not want to copy the data as it may be very large. (The main thread also keeps a "cache" of the data in-memory anyway.)

I can achieve this with Arc<RwLock<T>>, where T being my data structure.

However, there is nothing stopping the side threads updating the data. The side threads can simply call lock() and the write to the data.

My question is there something similar to RwLock where the owner/creator of it has the only write access, but all other instances have read-only access? This way I will have compile time checking of any logic bugs that may occur via side threads accidentally updating data.

Regarding these questions:

The above questions suggest solving it with Arc<Mutex<T>> or Arc<RwLock<T>> which is all fine. But it still doesn't give compile time enforcement of only one writer.

Additionally: crossbeam or rayon's scoped threads don't help here as I want my side threads to outlive my main thread.

6
  • 5
    You can easily create a read-only wrapper type Read<T> that wraps Arc<RwLock<T>> and keeps the inner Arc<RwLock<T>> private. Read<T> would only expose the read() method of the inner RwLock, so the code that receives it couldn't modify the data. You'd send those wrappers to other threads, and keep the pristine Arc<RwLock<T>> in the main thread only. (You could even create a non-Send wrapper that holds Arc<RwLock<T>> in the main thread to avoid exposing them by accident - creation Read<T> would be done in a method on the wrapper.) Commented Aug 24, 2021 at 13:16
  • 2
    Here is an example of the wrapper. Commented Aug 24, 2021 at 13:22
  • That is a great idea! This is probably what I'll end up doing if there's nothing in the std that does this already. Commented Aug 24, 2021 at 13:27
  • 2
    Using an interface might help to cut out other behaviors, playground Commented Aug 24, 2021 at 14:10
  • Another solution, simpler but with only runtime checking, is to have the main thread keep a read lock for the duration. This will deadlock if a worker thread attempts to take the write lock. Commented Aug 24, 2021 at 15:08

1 Answer 1

12

You can create a wrapper type over an Arc<RwLock<T>> that only exposes cloning via a read only wrapper:

mod shared {
    use std::sync::{Arc, LockResult, RwLock, RwLockReadGuard, RwLockWriteGuard};

    pub struct Lock<T> {
        inner: Arc<RwLock<T>>,
    }

    impl<T> Lock<T> {
        pub fn new(val: T) -> Self {
            Self {
                inner: Arc::new(RwLock::new(val)),
            }
        }

        pub fn write(&self) -> LockResult<RwLockWriteGuard<'_, T>> {
            self.inner.write()
        }

        pub fn read(&self) -> LockResult<RwLockReadGuard<'_, T>> {
            self.inner.read()
        }

        pub fn read_only(&self) -> ReadOnly<T> {
            ReadOnly {
                inner: self.inner.clone(),
            }
        }
    }

    pub struct ReadOnly<T> {
        inner: Arc<RwLock<T>>,
    }

    impl<T> ReadOnly<T> {
        pub fn read(&self) -> LockResult<RwLockReadGuard<'_, T>> {
            self.inner.read()
        }
    }
}

Now you can pass read only versions of the value to spawned threads, and continue writing in the main thread:

fn main() {
    let val = shared::Lock::new(String::new());

    for _ in 0..10 {
        let view = val.read_only();
        std::thread::spawn(move || {
            // view.write().unwrap().push_str("...");
            // ERROR: no method named `write` found for struct `ReadOnly` in the current scope
            println!("{}", view.read().unwrap());
        });
    }

    val.write().unwrap().push_str("...");
    println!("{}", val.read().unwrap());
}
Sign up to request clarification or add additional context in comments.

1 Comment

For added safety, one could un-implement Send and Sync on Lock to prevent it from being sent or exposed to another thread by accident.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.