如何替换互斥锁中的值? [英] How can I replace the value inside a Mutex?

查看:49
本文介绍了如何替换互斥锁中的值?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个隐藏在 Mutex 后面的 Git 存储库:

I have a Git repository hidden behind a Mutex:

pub struct GitRepo {
    contents: Mutex<GitContents>,
    workdir: PathBuf,
}

我想查询它,但最多只能查询一次:查询后,我只想使用我们第一次得到的结果.存储库具有 git2::Repository 或结果向量.RepositorySend 但不是 Sync.

I want to query it, but only a maximum of once: after it's been queried, I want to just use the results we got the first time. A repository has either a git2::Repository, or a vector of results. A Repository is Send but not Sync.

enum GitContents {
    Before { repo: git2::Repository },
    After { statuses: Git },
}

struct Git {
    statuses: Vec<(PathBuf, git2::Status)>,
}

GitContents 枚举反映了这样一个事实:我们要么有要查询的存储库,要么有查询的结果,但从来没有.

The GitContents enum reflects the fact that we either have the repository to query, or the results of querying it, but never both.

我试图让 Rust 强制执行此属性,方法是使用将存储库转换为状态的函数 消费 存储库,因为它生成状态向量:

I'm trying to get Rust to enforce this property by having the function that turns a repository into statuses consume the repository as it produces the status vector:

fn repo_to_statuses(repo: git2::Repository, workdir: &Path) -> Git {
    // Assume this does something useful...
    Git { statuses: Vec::new() }
}

但是,我无法让 Mutex 很好地使用它.到目前为止,这是我尝试编写一个函数,该函数使用谓词 P 查询 GitRepo,如果没有,则替换 Mutex 中的值尚未被查询:

However, I can't get the Mutex to play nice with this. Here is my attempt so far to write a function that queries a GitRepo with a predicate P, replacing the value inside the Mutex if it hasn't been queried yet:

impl GitRepo {
    fn search<P: Fn(&Git) -> bool>(&self, p: P) -> bool {
        use std::mem::replace;
        // Make this thread wait until the mutex becomes available.
        // If it's locked, it's because another thread is running repo_to_statuses
        let mut contents = self.contents.lock().unwrap();
        match *contents {
            // If the repository has been queried then just use the existing results
            GitContents::After { ref statuses } => p(statuses),
            // If it hasn't, then replace it with some results, then use them.
            GitContents::Before { ref repo } => {
                let statuses = repo_to_statuses(*repo, &self.workdir);
                let result = p(&statuses);
                replace(&mut *contents, GitContents::After { statuses });
                result
            },
        }
    }
}

虽然涉及到mutation,但是这个方法只需要&self而不是&mut self,因为无论是否查询repository都返回相同的结果第一次或第二次,即使第一次有更多的工作要做.但 Rust 抱怨:

Although there is mutation involved, this method only takes &self rather than &mut self because it returns the same result regardless of whether the repository is being queried for the first or second time, even though there's more work being done on the first. But Rust complains:

  • 它拒绝将 repo 移出我在 repo_to_statuses(*repo, &self.workdir) 中借用的内容,即使我知道值之后应立即更换.(不能移出借来的内容")
  • 它也不喜欢我 replace-ing &mut *contents,因为我在借用内容不变,因为值是 match-ed.(不能借用 'contents' 为可变的,因为它也借用为不可变的")
  • It refuses to move the repo out of the contents I've borrowed in repo_to_statuses(*repo, &self.workdir), even though I know the value should get replaced immediately afterwards. ("cannot move out of borrowed content")
  • It doesn't like me replace-ing &mut *contents either, because I'm borrowing the contents immutably as the value being match-ed. ("cannot borrow 'contents' as mutable because it is also borrowed as immutable")

有什么方法可以说服借阅检查员我的意图吗?

Is there any way to convince the borrow checker of my intentions?

推荐答案

你问的问题和真正的内部问题与 Mutex 没有本质的关系,一旦你锁定它并拥有一个可变引用或一个实现 DerefMut 的类型.

The question you are asking and the real inner problem have nothing intrinsically to do with a Mutex, once you have locked it and have a mutable reference or a type that implements DerefMut.

您可以使用取消引用运算符 * 为引用分配一个新值.如果需要之前的值,可以使用 std::mem::替换.

You can assign a new value to the reference using the dereference operator *. If you need the previous value, you can use std::mem::replace.

use std::sync::Mutex;
use std::mem;

fn example_not_using_old_value(state: &Mutex<String>) {
    let mut state = state.lock().expect("Could not lock mutex");
    *state = String::from("dereferenced");
}

fn example_using_old_value(state: &Mutex<String>) -> String {
    let mut state = state.lock().expect("Could not lock mutex");
    mem::replace(&mut *state, String::from("replaced"))
}

fn main() {
    let state = Mutex::new("original".into());
    example_not_using_old_value(&state);
    let was = example_using_old_value(&state);

    println!("Is now {:?}", state);
    println!("Was {:?}", was);
}    

我们取消引用 MutexGuard<T> 以获得 T,并对其进行可变引用,产生一个 &mut T我们可以调用 mem::replace .

We dereference the MutexGuard<T> to get a T, and take a mutable reference to that, yielding a &mut T that we can call mem::replace with.

更广泛的问题是因为您无法移出借用的内容(请参阅 为此进行了大量问答).查看这些直接相关的问答:

Your broader problem is because you can't move out of borrowed content (see the numerous Q&A for that). See these directly relevant Q&A:

您可能希望添加一个新的 enum 变体,它表示所有内容都已移出但尚未移回任何内容时的状态.然后,您可以用该虚拟值替换您的值并获得旧值的所有权,执行您的操作,然后将新值放回.

You may wish to add a new enum variant that represents the state when everything's been moved out but nothing has been moved back in yet. Then you can replace your value with that dummy and take ownership of the old value, performing your operations, then putting the new value back.

这篇关于如何替换互斥锁中的值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆