在看tokio官方文档的时候,有描述使用标准库的锁和使用tokio的锁,说有个错误理解,是不是所有的异步代码都要用tokio的锁。还给了一条规则,但是没有理解什么叫跨await调用lock???
希望大佬给个例子,能复现错误使用且锁住最好?感谢
原文如下: On using std::sync::Mutex Note that std::sync::Mutex and not tokio::sync::Mutex is used to guard the HashMap. A common error is to unconditionally use tokio::sync::Mutex from within async code. An async mutex is a mutex that is locked across calls to .await.
A synchronous mutex will block the current thread when waiting to acquire the lock. This, in turn, will block other tasks from processing. However, switching to tokio::sync::Mutex usually does not help as the asynchronous mutex uses a synchronous mutex internally.
As a rule of thumb, using a synchronous mutex from within asynchronous code is fine as long as contention remains low and the lock is not held across calls to .await.
评论区
写评论实际应用的话,典型的场景就是在共享IO资源的时候使用tokio的Mutex,可以看看tokio的docs.rs,介绍的更详细
大致明白了。感谢各位大佬指导
这个代码就会报错。现在分析一下为什么会报错。 最直观的原因时因为async块生成的future是!Send的,因为Mutex lock生成的guard是!Send的,并且跨越了await,所以整个future就是!Send的。倘若没有跨过await,那就是Send。
现在分析一下为什么这样做,tokio默认是调度在多个线程上的,有多个执行流,并且同个future可能在不同的线程上运行,所以spawn要求Send+Sync+'static。
await点意味着future poll可能返回了pending,这时的future是有可能被tokio调度到其他线程运行的。
假如这段代码可以编译过去就会出现一个线程加锁,解锁在非加锁线程上的情况。所以此future是!Send。 倘若没有跨越await,自然也就保证了在同一个线程上加锁并且解锁的情况,自然future就是Send了。
这就叫跨 .await 占用 lock
通常情况下,我们希望 mutex 用完之后尽快释放掉。这个例子中执行完
*lock += 1
之后,lock 的任务已经完成了,没必要等到跨过后面的 .await 之后再释放因为 tokio 的 Mutex 允许这样用,所以当我们写出类似上面这种不符合最佳实践的代码时编译器不会有啥提示。而使用 std 的 Mutex 直接就不允许你跨 .await 持有 lock,逼你写出符合最佳的代码
所以只有当你自己明确知道,我的需求就是要在多个 .await 间持有 lock,这个时候用 tokio 的 Mutex 才合适
这段代码会报错:
这段不会报错
https://tokio.rs/tokio/tutorial/shared-state
简单来说,MutexGuard生命周期必须跨越await的时候可以使用async,其他情况可以优先考虑sync