我正在try 实现一个共享状态、线程安全的缓存,在RwLock
后面使用HashMap
.理想情况下,我希望我的接口公开一个函数,该函数--给定键--将返回对缓存中的值的引用(如果需要,首先填充它).然而,我的中级直觉告诉我,这种方法至少会遇到以下问题:
-
是否有可能返回对值的引用?AFAIK,
HashMap
可以在需要时自由重新分配(*),因此引用的生命周期不同于HashMap
,并且实际上是不确定的(就编译器而言).HashMap::entry
API返回一个引用,但当然,它的生存时间不能超过它的封闭作用域.因此,是否有必要返回一个具体的值,从而强制我为值类型实现Copy
?(*)如果值不实现
Copy
,这是否仍然正确? -
RwLock
对我来说是新的.我从Mutex
开始,但RwLock
看起来更合适,因为对缓存的预期访问模式(即,每个键一次写入,多次读取).然而,我不确定是否可以将读锁"升级"为读写锁.Rust文档表明,读锁将阻止写锁,这将导致我建议/希望它工作的方式出现死锁.我是否可以取消读锁定并将其替换为写锁定,或者从一开始就使用完整的Mutex
更容易?
顺便说一句,获取该值的函数是异步的,所以虽然我可以使用HashMap::entry
API,但我不能使用Entry
个便利函数(如or_insert
)……
不管怎样,也许我有点不知所措,但这就是我到目前为止所得到的:
// NOTE The `Key` struct contains a reference, with lifetime `'key`
pub struct Cache<'key>(RwLock<HashMap<Key<'key>, Value>>);
impl<'cache, 'key> Cache<'key> {
pub fn new() -> Arc<Self> {
Arc::new(Self(RwLock::new(HashMap::new())))
}
// NOTE The key and value for the cache is derived from the `Payload` type
pub async fn fetch(
&'cache mut self,
data: &'cache Payload<'key>
) -> Result<&'cache Value> {
let cache = self.0.read()?;
let key = data.into();
Ok(match &mut cache.entry(key) {
// Match arm type: &Value
// FIXME This reference outlives the scope, which is a borrow violation
Entry::Occupied(entry) => entry.get(),
// Dragons be here...
Entry::Vacant(ref mut slot) => {
// FIXME How do I create a write lock here, without deadlocking on
// the read lock that's still in scope?
let value = data.get_value().await?;
// FIXME `VacantEntry<'_, Key<'_>, Value>` doesn't implement `Copy`
slot.insert(value)
}
})
}
}