Reputation: 763
I am relatively new to rust, and I want to use Arc<>
to give immutable access to some data to many threads, however I want to be able for the original owner of that Arc
to have write access without introducing any kind of lock (mutex, rwlock etc). My application guarantees that there is only ever a single thread producing (writing) to the data inside the arc (so never a write data race to the arc). But there are many threads reading the data in the arc. (reading, not consuming). How do I implement this? Whenever I go near Arc
and try to mutate I am not allowed to. Obviously I understand unsafe is needed for this.
The bus is created and cloned over to the reader threads.
use std::borrow::BorrowMut;
use std::sync::Arc;
type Data<T> = Arc<Vec<T>>;
pub struct Bus<T> {
data: Data<T>
}
impl<T: Clone + Send + Sync + 'static> Bus<T> {
pub fn new() -> Self {
let data: Data<T> = Arc::new(Vec::new());
{
let mut data = data.clone();
std::thread::spawn(move || {
let a = data.borrow_mut();
a.push(value); // write to vector
}).join();
}
Bus {
data
}
}
pub fn read(&self, idx: usize) -> T {
self.data[idx].clone()
}
}
gives:
cannot borrow data in an
Arc
as mutable
Upvotes: 1
Views: 680
Reputation: 3763
No, if a Vec<T>
is written by one thread and read by multiple threads, here is a data race.
There are some other lock-free options, but they all different from your example.
If T
is a simple type (a common integer type or a type with appropriate length and Copy
trait implemented) and the Vec
length is fixed, you can use a fixed-length array + atomic type. You can safely read and write each element, but cannot grow the array.
Crate atomic
provides a Atomic<T>
type and you can check if it is lock-free at compile time.
AFAIK, a lock-free Vec
is not possible, however some crates offer lock-free queues. But obviously a queue cannot be accessed by an index.
Upvotes: 0
Reputation: 10186
Disclaimer - Concurrent data structures are hard and I'm not an expert in them
The error message you get is essentially saying "you can't mutate the contents of an Arc
, because other people could be reading it while you change it, causing a data race.
The typical solution is to use an Arc<Mutex<T>>
. A Mutex
allows you to go from an immutable reference to a mutable reference by first locking the mutex.
However, you've indicated you don't want to use locking.
So if Mutex
(and similar things like RwLock
are off the table, you'll need a Vec
-like data structure that allows mutation via immutable reference.
However, my understanding is there's generally very little reason to prefer a "concurrent" or "lock free" Vec
over a simple Arc<Mutex<Vec<T>>
(or probably an Arc<RwLock<T>>
in your case). In general, the performance will be similar.
This reddit thread has some insight into why such data structures don't really exist if you want some further reading.
There are also concurrent HashMap
-like structures if you find that model is also suitable. Maps are generally more amenable to this kind of lock-free or highly concurrent workload.
For example, chashmap
uses per-bin locking (i.e. each bin in the map has its own lock), so lock contention stays relatively low.
There is also evmap
which is entirely lock-free, but comes at the cost of being only eventually consistent and using ~2x the memory. It essentially maintains 2 copies of your map, one of which is a "read map" and the other is a "write map". You can then "publish" the changes to apply the writes.
Upvotes: 2