Rust 中如何解决从受互斥锁保护的对象获取独立数据后必须保留锁的问题?

问题描述 投票:0回答:1

在actix-web路由功能中,我发现从受互斥锁保护的对象获取独立数据后,互斥锁必须处于锁定状态才能使用这些数据。如果互斥体仅在获取数据时加锁,则使用数据时会出现编译错误。这是可以直接编译的代码:

use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::Mutex;
use async_trait::async_trait;
use actix_web::{web, App, HttpServer, HttpResponse, Result as ActixResult};

#[async_trait]
pub trait Reader: Send + Debug { }

#[derive(Clone, Debug)]
pub struct RemoteReader;

#[async_trait]
impl Reader for RemoteReader { }

#[derive(Default)]
pub struct DataReaders(HashMap<String, Box<dyn Reader>>);

impl DataReaders {
    pub fn get_readers(&self, ids: &str) -> ActixResult<Vec<&dyn Reader>> {
        let mut readers = Vec::new();
        for id in ids.split(',') {
            readers.push(self.0.get(id).unwrap().as_ref());
        }
        Ok(readers)
    }
}

async fn get(data: web::Data<Mutex<DataReaders>>) -> ActixResult<HttpResponse> {
    let mut readers = data.lock().unwrap().get_readers("1,2")?;
    if readers.is_empty() {
        return Ok(HttpResponse::NotFound().finish())
    }
    // Below will call multiple asynchronous functions, so I do not want to
    // hold the lock of DataReaders for a long time.
    readers.clear();
    Ok(HttpResponse::Ok().finish())
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let mut readers = DataReaders{ 0: Default::default() };
    readers.0.insert("1".to_string(), Box::new(RemoteReader{}));
    readers.0.insert("2".to_string(), Box::new(RemoteReader{}));
    let counter = web::Data::new(Mutex::new(readers));

    HttpServer::new(move || {
        App::new()
            .app_data(counter.clone())
            .route("/", web::get().to(get))
    }).bind(("127.0.0.1", 8080))?.run().await
}

编译此代码并出现错误:

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:30:23
   |
30 |     let mut readers = data.lock().unwrap().get_readers("1,2")?;
   |                       ^^^^^^^^^^^^^^^^^^^^                    - temporary value is freed at the end of this statement
   |                       |
   |                       creates a temporary value which is freed while still in use
31 |     if readers.is_empty() {
   |        ------- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

我只想使用互斥锁来保护 HashMap,因为它会在其他路由函数中被更改。我认为从“get_readers”返回的 Vec<&dyn Reader> 对象不应该受到保护,因为其中的所有项目都是只读的。 那么我该如何解决这个问题呢?谢谢!

multithreading rust
1个回答
0
投票

如果在此期间有另一条路线插入到

HashMap
中,则可能需要重新分配,并且您持有的
&dyn Reader
引用将失效。事实上,Rust 不知道其他编写者是否可能更改或删除您正在使用的
HashMap
项。因此,除非您从 HashMap 克隆正在使用的内容,否则
Mutex
将需要保持锁定状态。

考虑将特征对象保存在重新计数的分配中,例如

Arc<dyn Reader>
,而不是
Box
。您可以廉价地克隆它们,然后放弃锁定:

use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use actix_web::{web, App, HttpServer, HttpResponse, Result as ActixResult};

#[async_trait]
pub trait Reader: Send + Debug { }

#[derive(Clone, Debug)]
pub struct RemoteReader;

#[async_trait]
impl Reader for RemoteReader { }

#[derive(Default)]
pub struct DataReaders(HashMap<String, Arc<dyn Reader>>);

impl DataReaders {
    pub fn get_readers(&self, ids: &str) -> ActixResult<Vec<Arc<dyn Reader>>> {
        let mut readers = Vec::new();
        for id in ids.split(',') {
            readers.push(self.0.get(id).unwrap().clone());
        }
        Ok(readers)
    }
}

async fn get(data: web::Data<Mutex<DataReaders>>) -> ActixResult<HttpResponse> {
    let mut readers = data.lock().unwrap().get_readers("1,2")?;
    if readers.is_empty() {
        return Ok(HttpResponse::NotFound().finish())
    }
    // Below will call multiple asynchronous functions, so I do not want to
    // hold the lock of DataReaders for a long time.
    readers.clear();
    Ok(HttpResponse::Ok().finish())
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let mut readers = DataReaders{ 0: Default::default() };
    readers.0.insert("1".to_string(), Arc::new(RemoteReader{}));
    readers.0.insert("2".to_string(), Arc::new(RemoteReader{}));
    let counter = web::Data::new(Mutex::new(readers));

    HttpServer::new(move || {
        App::new()
            .app_data(counter.clone())
            .route("/", web::get().to(get))
    }).bind(("127.0.0.1", 8080))?.run().await
}
© www.soinside.com 2019 - 2024. All rights reserved.