pub struct RwLock<T, Lock = Spinlock>{ /* private fields */ }
Expand description
An asynchronous readers-writer lock.
This type of lock protects shared data by allowing either multiple concurrent readers (shared access), or a single writer (exclusive access) at a given point in time. If the shared data must be modified, write access must be acquired, preventing other threads from accessing the data while it is being written, but multiple threads can read the shared data when it is not being mutated.
This is in contrast to a Mutex
, which only ever allows a single
core/thread to access the shared data at any point in time. In some cases,
such as when a large number of readers need to access the shared data
without modifying it, using a RwLock
can be more efficient than a
Mutex
.
Usage
The type parameter T
represents the data that this lock protects. It is
required that T
satisfies Send
to be shared across threads and
Sync
to allow concurrent access through readers. The RAII guards
returned from the locking methods implement Deref
(and DerefMut
for the write
methods) to allow access to the content of the lock.
The read
method acquires read access to the lock, returning a
RwLockReadGuard
. If the lock is currently locked for write access, the
read
method will wait until the write access completes before allowing
read access to the locked data.
The write
method acquires write access to the lock, returning a
RwLockWriteGuard
, which implements DerefMut
. If the lock is
currently locked for reading or writing, the write
method will wait
until all current reads or the current write completes before allowing write
access to the locked data.
Priority Policy
The priority policy of this lock is fair (or write-preferring), in
order to ensure that readers cannot starve writers. Fairness is ensured
using a first-in, first-out queue for the tasks awaiting the lock; if a task
that wishes to acquire the write lock is at the head of the queue, read
locks will not be given out until the write lock has been released. This is
in contrast to the Rust standard library’s std::sync::RwLock
, where the
priority policy is dependent on the operating system’s implementation.
Overriding the blocking mutex
This type uses a blocking Mutex
internally to
synchronize access to its wait list. By default, this is a Spinlock
. To
use an alternative RawMutex
implementation, use the
new_with_raw_mutex
constructor. See the documentation
on overriding mutex
implementations for more
details.
Note that this type currently requires that the raw mutex implement
RawMutex
rather than mutex_traits::ScopedRawMutex
!
Examples
use maitake_sync::RwLock;
let lock = RwLock::new(5);
// many reader locks can be held at once
{
let r1 = lock.read().await;
let r2 = lock.read().await;
assert_eq!(*r1, 5);
assert_eq!(*r2, 5);
} // read locks are dropped at this point
// only one write lock may be held, however
{
let mut w = lock.write().await;
*w += 1;
assert_eq!(*w, 6);
} // write lock is dropped here
Implementations§
§impl<T> RwLock<T>where
T: ?Sized,
impl<T> RwLock<T>where
T: ?Sized,
pub async fn read_owned(self: &Arc<RwLock<T>>) -> OwnedRwLockReadGuard<T>
pub async fn read_owned(self: &Arc<RwLock<T>>) -> OwnedRwLockReadGuard<T>
Locks this RwLock
with shared read access, returning an owned RAII
guard.
This method is identical to RwLock::read
, execept that it requires
the RwLock
to be wrapped in an Arc
, and returns an
OwnedRwLockReadGuard
that clones the Arc
rather than
borrowing the lock. Therefore, the returned guard is valid for the
'static
lifetime.
If the lock is locked for write access, the calling task will yield and wait until there are no writers which hold the lock. There may be other readers inside the lock when the task resumes.
Note that under the priority policy of RwLock
, read locks are not
granted until prior write locks, to prevent starvation. Therefore
deadlock may occur if a read lock is held by the current task, a write
lock attempt is made, and then a subsequent read lock attempt is made
by the current task.
Returns an RAII guard which will release this read access of the
RwLock
when dropped.
Cancellation
This method uses a queue to fairly distribute locks
in the order they were requested. Cancelling a call to read
results
in the calling task losing its place in the queue.
Examples
use maitake_sync::RwLock;
use alloc::sync::Arc;
let lock = Arc::new(RwLock::new(1));
// hold the lock for reading in `main`.
let n = lock
.try_read()
.expect("read lock must be acquired, as the lock is unlocked");
assert_eq!(*n, 1);
task::spawn({
let lock = lock.clone();
async move {
// While main has an active read lock, this task can acquire
// one too.
let n = lock.read_owned().await;
assert_eq!(*n, 1);
}
});
pub async fn write_owned(self: &Arc<RwLock<T>>) -> OwnedRwLockWriteGuard<T>
pub async fn write_owned(self: &Arc<RwLock<T>>) -> OwnedRwLockWriteGuard<T>
Locks this RwLock
with exclusive write access,returning an owned RAII
guard.
This method is identical to RwLock::write
, execept that it requires
the RwLock
to be wrapped in an Arc
, and returns an
OwnedRwLockWriteGuard
that clones the Arc
rather than
borrowing the lock. Therefore, the returned guard is valid for the
'static
lifetime.
Returns
If other tasks are holding a read or write lock, the calling task will wait until the write lock or all read locks are released.
Returns an RAII guard which will release the write access of this
RwLock
when dropped.
Cancellation
This method uses a queue to fairly distribute
locks in the order they were requested.
Cancelling a call to write
results in the calling task losing its place
in the queue.
Examples
use maitake_sync::RwLock;
use alloc::sync::Arc;
let lock = Arc::new(RwLock::new(1));
task::spawn(async move {
let mut guard = lock.write_owned().await;
*guard += 1;
});
pub fn try_read_owned(self: &Arc<RwLock<T>>) -> Option<OwnedRwLockReadGuard<T>>
pub fn try_read_owned(self: &Arc<RwLock<T>>) -> Option<OwnedRwLockReadGuard<T>>
Attempts to acquire this RwLock
for shared read access, without
waiting, and returning an owned RAII guard.
This method is identical to RwLock::try_read
, execept that it requires
the RwLock
to be wrapped in an Arc
, and returns an
OwnedRwLockReadGuard
that clones the Arc
rather than
borrowing the lock. Therefore, the returned guard is valid for the
'static
lifetime.
Returns
If the access couldn’t be acquired immediately, this method returns
None
rather than waiting.
Otherwise, an RAII guard is returned, which allows read access to the protected data and will release that access when dropped.
Examples
use maitake_sync::RwLock;
use alloc::sync::Arc;
let lock = Arc::new(RwLock::new(1));
let mut write_guard = lock
.try_write()
.expect("lock is unlocked, so write access should be acquired");
*write_guard += 1;
// because a write guard is held, we cannot acquire the read lock, so
// this will return `None`.
assert!(lock.try_read_owned().is_none());
pub fn try_write_owned(
self: &Arc<RwLock<T>>
) -> Option<OwnedRwLockWriteGuard<T>>
pub fn try_write_owned( self: &Arc<RwLock<T>> ) -> Option<OwnedRwLockWriteGuard<T>>
Attempts to acquire this RwLock
for exclusive write access, without
waiting, and returning an owned RAII guard.
This method is identical to RwLock::try_write
, execept that it requires
the RwLock
to be wrapped in an Arc
, and returns an
OwnedRwLockWriteGuard
that clones the Arc
rather than
borrowing the lock. Therefore, the returned guard is valid for the
'static
lifetime.
Returns
If the access couldn’t be acquired immediately, this method returns
None
rather than waiting.
Otherwise, an RAII guard is returned, which allows write access to the protected data and will release that access when dropped.
Examples
use maitake_sync::RwLock;
use alloc::sync::Arc;
let lock = Arc::new(RwLock::new(1));
let read_guard = lock
.try_read()
.expect("lock is unlocked, so read access should be acquired");
assert_eq!(*read_guard, 1);
// because a read guard is held, we cannot acquire the write lock, so
// this will return `None`.
assert!(lock.try_write_owned().is_none());
§impl<T> RwLock<T>
impl<T> RwLock<T>
pub const fn new(data: T) -> RwLock<T>
pub const fn new(data: T) -> RwLock<T>
Returns a new RwLock
protecting the provided data
, in an
unlocked state.
This constructor returns a RwLock
that uses a Spinlock
as the
underlying blocking mutex implementation. To use an alternative
RawMutex
implementation, use the RwLock::new_with_raw_mutex
constructor instead. See the documentation on overriding mutex
implementations
for more details.
Examples
use maitake_sync::RwLock;
let lock = RwLock::new(5);
Because this is a const fn
, it may be used in static
initializers:
use maitake_sync::RwLock;
static LOCK: RwLock<usize> = RwLock::new(5);
§impl<T, Lock> RwLock<T, Lock>where
Lock: RawMutex,
impl<T, Lock> RwLock<T, Lock>where
Lock: RawMutex,
pub const fn new_with_raw_mutex(data: T, lock: Lock) -> RwLock<T, Lock>
pub const fn new_with_raw_mutex(data: T, lock: Lock) -> RwLock<T, Lock>
Returns a new RwLock
protecting the provided data
, in an
unlocked state, using the provided RawMutex
implementation.
This constructor allows a RwLock
to be constructed with any type that
implements RawMutex
as the underlying raw blocking mutex
implementation. See the documentation on overriding mutex
implementations
for more details.
pub fn into_inner(self) -> T
pub fn into_inner(self) -> T
Consumes this RwLock
, returning the guarded data.
§impl<T, Lock> RwLock<T, Lock>
impl<T, Lock> RwLock<T, Lock>
pub async fn read(&self) -> RwLockReadGuard<'_, T, Lock>
pub async fn read(&self) -> RwLockReadGuard<'_, T, Lock>
Locks this RwLock
with shared read access, causing the current task
to yield until the lock has been acquired.
If the lock is locked for write access, the calling task will yield and wait until there are no writers which hold the lock. There may be other readers inside the lock when the task resumes.
Note that under the priority policy of RwLock
, read locks are not
granted until prior write locks, to prevent starvation. Therefore
deadlock may occur if a read lock is held by the current task, a write
lock attempt is made, and then a subsequent read lock attempt is made
by the current task.
Returns [an RAII guard] which will release this read access of the
RwLock
when dropped.
Cancellation
This method uses a queue to fairly distribute locks
in the order they were requested. Cancelling a call to read
results
in the calling task losing its place in the queue.
Examples
use maitake_sync::RwLock;
use alloc::sync::Arc;
let lock = Arc::new(RwLock::new(1));
// hold the lock for reading in `main`.
let n = lock
.try_read()
.expect("read lock must be acquired, as the lock is unlocked");
assert_eq!(*n, 1);
task::spawn({
let lock = lock.clone();
async move {
// While main has an active read lock, this task can acquire
// one too.
let n = lock.read().await;
assert_eq!(*n, 1);
}
});
[an RAII guard]:
pub async fn write(&self) -> RwLockWriteGuard<'_, T, Lock>
pub async fn write(&self) -> RwLockWriteGuard<'_, T, Lock>
Locks this RwLock
with exclusive write access, causing the current
task to yield until the lock has been acquired.
If other tasks are holding a read or write lock, the calling task will wait until the write lock or all read locks are released.
Returns [an RAII guard] which will release the write access of this
RwLock
when dropped.
Cancellation
This method uses a queue to fairly distribute
locks in the order they were requested.
Cancelling a call to write
results in the calling task losing its place
in the queue.
Examples
use maitake_sync::RwLock;
use alloc::sync::Arc;
let lock = Arc::new(RwLock::new(1));
task::spawn(async move {
let mut guard = lock.write().await;
*guard += 1;
});
pub fn try_read(&self) -> Option<RwLockReadGuard<'_, T, Lock>>
pub fn try_read(&self) -> Option<RwLockReadGuard<'_, T, Lock>>
Attempts to acquire this RwLock
for shared read access, without
waiting.
If the access couldn’t be acquired immediately, this method returns
None
rather than waiting.
Otherwise, an RAII guard is returned, which allows read access to the protected data and will release that access when dropped.
Examples
use maitake_sync::RwLock;
let lock = RwLock::new(1);
let mut write_guard = lock
.try_write()
.expect("lock is unlocked, so write access should be acquired");
*write_guard += 1;
// because a write guard is held, we cannot acquire the read lock, so
// this will return `None`.
assert!(lock.try_read().is_none());
pub fn try_write(&self) -> Option<RwLockWriteGuard<'_, T, Lock>>
pub fn try_write(&self) -> Option<RwLockWriteGuard<'_, T, Lock>>
Attempts to acquire this RwLock
for exclusive write access, without
waiting.
If the access couldn’t be acquired immediately, this method returns
None
rather than waiting.
Otherwise, an RAII guard is returned, which allows write access to the protected data and will release that access when dropped.
Examples
use maitake_sync::RwLock;
let lock = RwLock::new(1);
let read_guard = lock
.try_read()
.expect("lock is unlocked, so read access should be acquired");
assert_eq!(*read_guard, 1);
// because a read guard is held, we cannot acquire the write lock, so
// this will return `None`.
assert!(lock.try_write().is_none());
pub fn get_mut(&mut self) -> &mut T
pub fn get_mut(&mut self) -> &mut T
Returns a mutable reference to the underlying data.
Since this call borrows the RwLock
mutably, no actual locking needs to
take place – the mutable borrow statically guarantees no locks exist.
Examples
let mut lock = maitake_sync::RwLock::new(0);
*lock.get_mut() = 10;
assert_eq!(*lock.try_read().unwrap(), 10);