【问题标题】:Mutable borrow to object inside Mutex - how to refactor?可变借用到 Mutex 内部的对象 - 如何重构?
【发布时间】:2019-11-27 11:39:22
【问题描述】:

我的许多函数都有以下模式:

use std::sync::{Arc, Mutex};

struct State { 
    value: i32
}

fn foo(data: Arc<Mutex<State>>) {
    let state = &mut data.lock().expect("Could not lock mutex");
    // mutate `state`
}

&amp;mut *data.lock().expect("Could not lock mutex") 一遍又一遍地重复,所以我想将它重构为一个函数,以便编写类似的东西

let state = get_state(data); 

我尝试了以下方法:

fn get_state(data: &Arc<Mutex<State>>) -> &mut State {
    &mut data.lock().expect("Could not lock mutex")
}

编译失败:

错误:无法返回引用临时值的值

这让我相信data.state.lock().expect("...") 按价值返回。但是,我可以看到通过多次foo 调用on this playground 改变了状态。

这是怎么回事?为什么我看似简单的重构编译失败?


编辑:

我希望以下内容也能正常工作:

fn get_state<'a>(data: &'a Arc<Mutex<State>>) -> &'a mut State {
    let state: &'a mut State = &mut data.lock().expect("Could not lock mutex");
    state
}

但它失败了:

   |
12 | fn get_state<'a>(data: &'a Arc<Mutex<State>>) -> &'a mut State {
   |              -- lifetime `'a` defined here
13 |     let state: &'a mut State = &mut data.lock().expect("Could not lock mutex");
   |                -------------        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ creates a temporary which is freed while still in use
   |                |
   |                type annotation requires that borrow lasts for `'a`
14 |     state
15 | }
   | - temporary value is freed at the end of this statement

为什么从lock 返回的内容的生命周期与data 参数之一不匹配?

【问题讨论】:

  • lock() 返回 MutexGuard 而不是值本身。您可以访问存储在其中的值,因为它实现了DerefDerefMut,但您仍然在引用互斥锁。当互斥锁超出范围时,您的引用将指向已释放的内存,因此 rust 阻止了这种情况的发生。 (请记住,一旦互斥锁超出范围,互斥锁将被释放!!!) PS:与其将.expect() 隐藏在另一种方法中,不如正确处理错误情况
  • @SvetlinZarev:这是有道理的。假设我不使用expect,我确实处理了错误,但每个函数的错误处理都是相同的。同样,在这种情况下如何避免重复?我能想到的唯一方法是高阶函数 - 这是惯用的吗?
  • 我真的不能说什么是惯用的,但我的观察是,常见的重复性任务通常由宏处理。
  • 如果将结果类型从&amp;mut State 更改为MutexGuard&lt;State&gt;,重构将是有效的。 @Svetlin:我想这就是你想要表达的意思吗?
  • 我的意思是不能绕过MutexGuard。如果 OP 返回 MutexGuard -> 那么应该可以。

标签: rust refactoring mutex borrowing


【解决方案1】:

lock() 方法返回MutexGuard,而不是对受保护对象的直接引用。您可以使用对象引用,因为MutexGuard 实现了DerefDerefMut,但您仍然需要mutex-guard 在范围内,因为当它超出范围时,互斥锁将被释放。此外,对内部对象的引用的生命周期与互斥保护的生命周期绑定,因此编译器将不允许您在没有互斥保护的情况下使用对内部对象的引用。

您可以在宏或方法中提取常用逻辑,但它应该返回 MutexGuard 而不是对内部对象的引用。

【讨论】:

    【解决方案2】:

    一种抽象锁定和解锁互斥锁的方法是让 API 接受一个闭包并将解锁的引用传递给它。

    fn with_state<R>(data: Arc<Mutex<State>>, f: impl FnOnce(&mut State) -> R) -> R {
        let state = &mut data.lock().expect("Could not lock mutex");
        f(state)
    }
    

    给定with_state,你可以写成foo如下:

    fn foo(data: Arc<Mutex<State>>) {
        with_state(data, |state| state.value += 1)
    }
    

    这类似于像 crossbeam 这样的 crate 保证作用域线程总是加入的方式。它比返回 MutexGuard 更严格,因为当您调用 with_state 时,可以保证在闭包返回后删除守卫。另一方面,返回 MutexGuard 更通用,因为您可以将 with_state 写成返回保护的函数,但不能反过来(使用 with_state 编写一个函数返回一名守卫)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-02-02
      • 2016-05-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-06
      相关资源
      最近更新 更多