Whether your code deadlocks depends on the conditions your threads release a lock again. If releasing a lock won't wait on another thread doing something, you should be fine. If not, then you need to be careful.
Apart from deadlocks, I think there is the problem that if you have many readers, a writer might wait for an indefinite time. Consider a database that has 1000 of readers per second, and sometimes you want to update the database. If the database uses a single std::sync::RwLock
in this thought experiment, then an updating task might not be able to aquire the lock until no users access the database for read operations (e.g. at night).
Before I moved to locks that have better guarantees for properties (e.g. parking_lot
), I used the following trick to circumvent that issue:
use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
type Data = i32;
struct Database {
storage: Arc<RwLock<Data>>,
prelock: Mutex<()>,
}
impl Database {
fn read(&self) -> RwLockReadGuard<'_, Data> {
let prelock = self.prelock.lock().unwrap();
let result = self.storage.read().unwrap();
drop(prelock);
result
}
fn write(&mut self) -> RwLockWriteGuard<'_, Data> {
let prelock = self.prelock.lock().unwrap();
let result = self.storage.write().unwrap();
drop(prelock);
result
}
}
fn main() {
let mut db = Database {
storage: Arc::new(RwLock::new(0)),
prelock: Mutex::new(()),
};
// Example write operation:
{
let mut handle = db.write();
*handle += 1;
}
// Example read operation:
{
let handle = db.read();
println!("Current value is {}", &*handle);
}
}
(Playground)
Depending on the particular implementation of Mutex
, this might still cause problems, I think, but at least both the readers and the writers will have to aquire the lock on a Mutex<()>
first. If there are no writers, then multiple readers may still concurrently access the database, as the prelock
(Mutex
) is only held while aquiring the RwLock
in read mode and released quickly. However, if a writer needs to access the database, it may lock the prelock
(Mutex
) and keep it locked while waiting for a write-lock on the other RwLock
. Then, new readers may not concurrently aquire a read-lock on the RwLock
anymore, and after all readers that started their work previously have finished, the writer will be granted the write-lock.
Note that if you do async programming, it may also be necessary to use an async-aware lock. In that case, you might be interested in the note "Which kind of mutex should I use" in tokio::sync::Mutex
's documentation.