2个版本
新 0.1.1 | 2024年8月19日 |
---|---|
0.1.0 | 2024年6月1日 |
在 异步 中排名 #326
每月下载 113 次
46KB
679 行
BorrowMutex
非常初版!请谨慎使用
BorrowMutex
是一个不需要封装目标结构的异步Mutex。相反,可以在任何给定时间将 &mut T
借给互斥锁。
这允许任何其他方借用 &mut T
。在出借方等待期间,可变引用是可借用的,出借方可以等待直到有人想要借用。语义强制至少在一方在任何给定时间只有一个可变引用。
这让我们可以在没有 Arc
<Mutex
> 对象的情况下,在没有依赖任何类型的内部可变性,共享任何可变对象。它主要针对单线程执行器,其中内部可变性是一个不必要的复杂化。尽管如此,BorrowMutex
是 Send+Sync,并且可以在任何数量的线程中安全使用。
最常见的用例是有一个完全在它自己的异步上下文中处理的状态,但偶尔需要从外部访问——另一个异步上下文。
由于共享数据不需要在 Arc
内部包装,因此它不需要在堆上分配。事实上,BorrowMutex 完全没有进行任何分配。在 tests/borrow_basic.rs
中提供了一个简单的示例,其中 所有内容 都存储在栈上。
安全性
当忘记未来([core::mem::forget()
]) 时,API 不稳定。为了方便,API 中没有任何标记为不安全的。
有关详细信息,请参阅 BorrowMutex::lend
。
希望未来的 Rust 版本可以通过额外的编译器注解禁止不稳定代码。
示例
use borrow_mutex::BorrowMutex;
use futures::FutureExt;
struct TestObject {
counter: usize,
}
let mutex = BorrowMutex::<16, TestObject>::new();
let f1 = async {
// try to borrow, await, and repeat until we get an Err.
// The Err can be either:
// - the mutex has too many concurrent borrowers (in this example we
// have just 1, and the max was 16)
// - the mutex was terminated - i.e. because the lending side knows it
// won't lend anymore
// We eventually expect the latter here
while let Ok(mut test) = mutex.request_borrow().await {
test.counter += 1; // mutate the object!
println!("f1: counter: {}", test.counter);
drop(test);
// `test` is dropped, and so the mutex.lend().await on the
// other side returns and can use the object freely again.
// we'll request another borrow in 100ms
smol::Timer::after(std::time::Duration::from_millis(100)).await;
}
};
let f2 = async {
let mut test = TestObject { counter: 1 };
// local object we'll be sharing
loop {
if test.counter >= 20 {
break;
}
// either sleep 200ms or lend if needed in the meantime
futures::select! {
_ = smol::Timer::after(std::time::Duration::from_millis(200)).fuse() => {
if test.counter < 10 {
test.counter += 1;
}
println!("f2: counter: {}", test.counter);
}
_ = mutex.wait_to_lend().fuse() => {
// there's someone waiting to borrow, lend
mutex.lend(&mut test).unwrap().await
}
}
}
mutex.terminate().await;
};
futures::executor::block_on(async {
futures::join!(f1, f2);
});
两个未来应该可以交替打印。有关完整的工作示例,请参阅 tests/borrow_basic.rs
。
如果未调用 Drop 会怎样?
不幸的是,会发生 未定义行为。当在 [core::mem::forget()
] 或类似的 LendGuard
上调用时,我们可以让借用检查器相信借出的 &mut T
已不再使用,而实际上它仍在使用
# use borrow_mutex::BorrowMutex;
# use futures::Future;
# use futures::task::Context;
# use futures::task::Poll;
# use core::pin::pin;
struct TestStruct {
counter: usize,
}
let mutex = BorrowMutex::<16, TestStruct>::new();
let mut test = TestStruct { counter: 1 };
let mut test_borrow = pin!(mutex.request_borrow());
let _ = test_borrow
.as_mut()
.poll(&mut Context::from_waker(&futures::task::noop_waker()));
let mut t1 = Box::pin(async {
mutex.lend(&mut test).unwrap().await;
});
let _ = t1
.as_mut()
.poll(&mut Context::from_waker(&futures::task::noop_waker()));
std::mem::forget(t1);
// the compiler thinks `test` is no longer borrowed, but in fact it is
let Poll::Ready(Ok(mut test_borrow)) = test_borrow
.as_mut()
.poll(&mut Context::from_waker(&futures::task::noop_waker()))
else {
panic!();
};
// now we get two mutable references, this is strictly UB
test_borrow.counter = 2;
test.counter = 6;
assert_eq!(test_borrow.counter, 2); // this fails
类似的 Rust 库正是由于这个原因使其 API 不安全 - 调用者的责任是不调用 [core::mem::forget()
] 或类似的 (async-scoped)
然而,这种未定义行为在常规代码中很难触发。在未来的对象上调用 [core::mem::forget()
] 几乎没有用处。