如何在非async场景下对futures::lock::Mutex实现非阻塞try_lock功能
解决方案
方法1:直接使用内置非阻塞try_lock方法
futures::lock::Mutex原生提供同步非阻塞的try_lock()接口,不需要async运行时环境即可直接调用,返回值为Option<MutexGuard<T>>:
- 加锁成功返回
Some(guard),可直接通过guard访问被保护的SmolStackWithDevice实例执行读操作 - 加锁失败直接返回
None,不会阻塞当前线程
这个方法完全匹配你需要的非阻塞加锁需求,不需要任何block_on或者async环境。
方法2:结构化异步等待锁(生产级稳妥方案)
直接用try_lock的问题在于,加锁失败后你需要手动处理唤醒逻辑,否则poll_read返回Pending后不会被重新调度。更标准的做法是把锁的异步获取Future存储在你的结构体中,分阶段poll:
use futures::lock::{Mutex, MutexLockFuture}; use std::pin::Pin; use std::task::{Context, Poll}; use tokio::io::{AsyncRead, ReadBuf}; use std::sync::Arc; pub struct SmolSocket<'a> { stack: Arc<Mutex<SmolStackWithDevice<'a>>>, // 存储正在进行的锁获取Future,Pin住避免自引用结构移动 lock_fut: Option<Pin<Box<MutexLockFuture<'a, SmolStackWithDevice<'a>>>>>, } impl<'a> AsyncRead for SmolSocket<'a> { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_> ) -> Poll<std::io::Result<()>> { // 优先处理锁获取逻辑 let lock_guard = match self.lock_fut.as_mut() { Some(fut) => { // 已有正在等待的锁请求,继续poll match fut.as_mut().poll(cx) { Poll::Ready(guard) => { // 拿到锁后清空存储的Future self.lock_fut.take(); guard } Poll::Pending => return Poll::Pending, } } None => { // 先走非阻塞快路径 match self.stack.try_lock() { Some(guard) => guard, None => { // 加锁失败则创建异步等待Future存储,下次poll继续处理 let fut = self.stack.lock(); self.lock_fut.insert(Box::pin(fut)).as_mut().poll(cx)?; return Poll::Pending; } } } }; // 拿到锁后执行读逻辑,若read是异步方法则需要额外存储读Future分阶段poll lock_guard.read(buf)?; Poll::Ready(Ok(())) } }
注意事项
- 绝对不要在
poll_*系列同步方法中调用block_on,会直接阻塞异步runtime的工作线程,导致整个服务调度卡住 - 如果
SmolStackWithDevice::read本身是异步方法,你需要在结构体中额外存储读操作的Future,和锁的处理逻辑一致分阶段poll - 优先用
try_lock走快路径,只有加锁失败时再创建异步Future走等待路径,可以最大化性能
内容的提问来源于stack exchange,提问作者Gatonito
相关产品推荐
相关产品推荐

