pub struct Mutex<T: ?Sized> { /* 私有字段 */ }展开描述
异步的类似 Mutex 的类型。
此类型的行为类似于 std::sync::Mutex,但有两个主要区别:lock 是一个异步方法,因此不会阻塞,并且锁保护器被设计为可以跨 .await 点持有。
Tokio 的 Mutex 在保证 FIFO(先进先出)的基础上运行。这意味着任务调用 lock 方法的顺序正是它们获取锁的顺序。
§Which kind of mutex should you use?
与普遍的看法相反,在异步代码中使用标准库的普通 Mutex 是可以的,而且通常是首选。
异步互斥锁相对于阻塞互斥锁提供的特性是能够跨 .await 点保持锁定。这使得异步互斥锁比阻塞互斥锁更昂贵,因此在使用阻塞互斥锁可行的场景下应首选阻塞互斥锁。异步互斥锁的主要用例是为 IO 资源(例如数据库连接)提供共享的可变访问。如果互斥锁后面的值只是数据,通常适合使用阻塞互斥锁,例如标准库中的互斥锁或 parking_lot。
请注意,尽管编译器不会阻止 std Mutex 在任务在线程之间不可移动的情况下跨 .await 点持有其保护器,但在实践中这几乎从不会产生正确的并发代码,因为它很容易导致死锁。
一种常见的模式是将 Arc<Mutex<...>> 包装在一个结构体中,该结构体提供用于对其中的数据执行操作的非异步方法,并且仅在这些方法内部锁定互斥锁。mini-redis 示例提供了此模式的说明。
此外,当你确实需要对 IO 资源的共享访问时,通常更好的做法是生成一个任务来管理 IO 资源,并使用消息传递与该任务通信。
§Examples:
use tokio::sync::Mutex;
use std::sync::Arc;
let data1 = Arc::new(Mutex::new(0));
let data2 = Arc::clone(&data1);
tokio::spawn(async move {
let mut lock = data2.lock().await;
*lock += 1;
});
let mut lock = data1.lock().await;
*lock += 1;use tokio::sync::Mutex;
use std::sync::Arc;
let count = Arc::new(Mutex::new(0));
for i in 0..5 {
let my_count = Arc::clone(&count);
tokio::spawn(async move {
for j in 0..10 {
let mut lock = my_count.lock().await;
*lock += 1;
println!("{} {} {}", i, j, lock);
}
});
}
loop {
if *count.lock().await >= 50 {
break;
}
}
println!("Count hit 50.");在此示例中有几点需要注意。
- The mutex is wrapped in an
Arcto allow it to be shared across threads. - Each spawned task obtains a lock and releases it on every iteration.
- Mutation of the data protected by the Mutex is done by de-referencing the obtained lock as seen on lines 13 and 20.
Tokio 的 Mutex 以简单的 FIFO(先进先出)方式工作,其中所有对 lock 的调用都按其执行的顺序完成。这样,Mutex 在如何将锁分配给内部数据方面是“公平”且可预测的。每次迭代后都会释放并重新获取锁,所以基本上,每个线程在将值递增一次后都会回到队尾。请注意,线程启动的时序存在一些不可预测性,但一旦它们开始运行,它们就会以可预测的方式交替。最后,由于任何给定时间都只有一个有效的锁,因此在变更内部值时不可能发生竞态条件。
请注意,与 std::sync::Mutex 相反,当持有 MutexGuard 的线程发生 panic 时,此实现不会使互斥锁中毒。在这种情况下,互斥锁将被解锁。如果 panic 被捕获,则受互斥锁保护的数据可能会处于不一致的状态。
实现§
源代码§impl<T: ?Sized> Mutex<T>
impl<T: ?Sized> Mutex<T>
源代码pub const fn const_new(t: T) -> Selfwhere
T: Sized,
pub const fn const_new(t: T) -> Selfwhere
T: Sized,
以未锁定状态创建一个新锁,可直接使用。
当使用 tracing 不稳定特性时,使用 const_new 创建的 Mutex 将不会被检测。因此,它将不会出现在 tokio-console 中。如果需要,应改用 Mutex::new 创建一个已检测的对象。
§示例
use tokio::sync::Mutex;
static LOCK: Mutex<i32> = Mutex::const_new(5);源代码pub async fn lock(&self) -> MutexGuard<'_, T>
pub async fn lock(&self) -> MutexGuard<'_, T>
锁定此互斥锁,使当前任务让出,直到获取到锁为止。获取锁后,函数返回一个 MutexGuard。
如果互斥锁可以立即获取,那么此调用通常不会让出执行权给运行时。但是,并非在所有情况下都能保证这一点。
§取消安全性
此方法使用一个队列来按请求顺序公平地分发锁。取消对 lock 的调用会使你失去在队列中的位置。
§示例
use tokio::sync::Mutex;
let mutex = Mutex::new(1);
let mut n = mutex.lock().await;
*n = 2;源代码pub fn blocking_lock(&self) -> MutexGuard<'_, T>
pub fn blocking_lock(&self) -> MutexGuard<'_, T>
阻塞地锁定此 Mutex。获取锁后,函数返回一个 MutexGuard。
此方法适用于需要在异步代码以及同步代码中使用此互斥锁的场景。
§恐慌
如果在异步执行上下文中调用,此函数会发生 panic。
- If you find yourself in an asynchronous execution context and needing
to call some (synchronous) function which performs one of these
blocking_operations, then consider wrapping that call insidespawn_blocking()(or [block_in_place()][crate::task::block_in_place]).
§示例
use std::sync::Arc;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() {
let mutex = Arc::new(Mutex::new(1));
let lock = mutex.lock().await;
let mutex1 = Arc::clone(&mutex);
let blocking_task = tokio::task::spawn_blocking(move || {
// This shall block until the `lock` is released.
let mut n = mutex1.blocking_lock();
*n = 2;
});
assert_eq!(*lock, 1);
// Release the lock.
drop(lock);
// Await the completion of the blocking task.
blocking_task.await.unwrap();
// Assert uncontended.
let n = mutex.try_lock().unwrap();
assert_eq!(*n, 2);
}源代码pub fn blocking_lock_owned(self: Arc<Self>) -> OwnedMutexGuard<T>
pub fn blocking_lock_owned(self: Arc<Self>) -> OwnedMutexGuard<T>
阻塞地锁定此 Mutex。获取锁后,函数返回一个 OwnedMutexGuard。
此方法与 Mutex::blocking_lock 相同,只是返回的保护器使用 Arc 而不是通过借用引用 Mutex。因此,Mutex 必须包装在 Arc 中才能调用此方法,并且保护器将在 'static 生命周期内有效,因为它通过持有 Arc 来保持 Mutex 存活。
§恐慌
如果在异步执行上下文中调用,此函数会发生 panic。
- If you find yourself in an asynchronous execution context and needing
to call some (synchronous) function which performs one of these
blocking_operations, then consider wrapping that call insidespawn_blocking()(or [block_in_place()][crate::task::block_in_place]).
§示例
use std::sync::Arc;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() {
let mutex = Arc::new(Mutex::new(1));
let lock = mutex.lock().await;
let mutex1 = Arc::clone(&mutex);
let blocking_task = tokio::task::spawn_blocking(move || {
// This shall block until the `lock` is released.
let mut n = mutex1.blocking_lock_owned();
*n = 2;
});
assert_eq!(*lock, 1);
// Release the lock.
drop(lock);
// Await the completion of the blocking task.
blocking_task.await.unwrap();
// Assert uncontended.
let n = mutex.try_lock().unwrap();
assert_eq!(*n, 2);
}源代码pub async fn lock_owned(self: Arc<Self>) -> OwnedMutexGuard<T>
pub async fn lock_owned(self: Arc<Self>) -> OwnedMutexGuard<T>
锁定此互斥锁,使当前任务让出,直到获取到锁为止。获取锁后,此方法返回一个 OwnedMutexGuard。
如果互斥锁可以立即获取,那么此调用通常不会让出执行权给运行时。但是,并非在所有情况下都能保证这一点。
此方法与 Mutex::lock 相同,只是返回的保护器使用 Arc 而不是通过借用引用 Mutex。因此,Mutex 必须包装在 Arc 中才能调用此方法,并且保护器将在 'static 生命周期内有效,因为它通过持有 Arc 来保持 Mutex 存活。
§取消安全性
此方法使用一个队列来按请求顺序公平地分发锁。取消对 lock_owned 的调用会使你失去在队列中的位置。
§示例
use tokio::sync::Mutex;
use std::sync::Arc;
let mutex = Arc::new(Mutex::new(1));
let mut n = mutex.clone().lock_owned().await;
*n = 2;源代码pub fn try_lock(&self) -> Result<MutexGuard<'_, T>, TryLockError>
pub fn try_lock(&self) -> Result<MutexGuard<'_, T>, TryLockError>
尝试获取锁,如果锁当前在其他位置被持有,则返回 TryLockError。
§示例
use tokio::sync::Mutex;
let mutex = Mutex::new(1);
let n = mutex.try_lock()?;
assert_eq!(*n, 1);源代码pub fn get_mut(&mut self) -> &mut T
pub fn get_mut(&mut self) -> &mut T
返回对底层数据的可变引用。
由于此调用可变地借用 Mutex,因此无需进行实际锁定——可变借用在静态上保证不存在锁。
§示例
use tokio::sync::Mutex;
fn main() {
let mut mutex = Mutex::new(1);
let n = mutex.get_mut();
*n = 2;
}源代码pub fn try_lock_owned(
self: Arc<Self>,
) -> Result<OwnedMutexGuard<T>, TryLockError>
pub fn try_lock_owned( self: Arc<Self>, ) -> Result<OwnedMutexGuard<T>, TryLockError>
尝试获取锁,如果锁当前在其他位置被持有,则返回 TryLockError。
此方法与 Mutex::try_lock 相同,只是返回的保护器使用 Arc 而不是通过借用引用 Mutex。因此,Mutex 必须包装在 Arc 中才能调用此方法,并且保护器将在 'static 生命周期内有效,因为它通过持有 Arc 来保持 Mutex 存活。
§示例
use tokio::sync::Mutex;
use std::sync::Arc;
let mutex = Arc::new(Mutex::new(1));
let n = mutex.clone().try_lock_owned()?;
assert_eq!(*n, 1);源代码pub fn into_inner(self) -> Twhere
T: Sized,
pub fn into_inner(self) -> Twhere
T: Sized,
消费互斥锁,返回底层数据。
§示例
use tokio::sync::Mutex;
let mutex = Mutex::new(1);
let n = mutex.into_inner();
assert_eq!(n, 1);