可变借用到 Mutex 内部的对象 - 如何重构?

Mutable borrow to object inside Mutex - how to refactor?

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

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`
}

&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")
}

编译失败:

ERROR: cannot return value referencing temporary value

这让我相信 data.state.lock().expect("...") returns 的价值。但是,我可以看到通过多次 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() 方法 returns MutexGuard 而不是对受保护对象的直接引用。您可以使用对象引用,因为 MutexGuard 实现了 DerefDerefMut,但是您仍然需要 mutex-guard 在范围内,因为当它超出范围时,互斥量锁将被释放。此外,对 iner 对象的引用的生命周期绑定到互斥保护的生命周期,因此编译器将不允许您在没有互斥保护的情况下使用对内部对象的引用。

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

抽象过度锁定和解锁互斥锁的一种方法是 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 这样的板条箱保证作用域线程始终加入的方式。它比返回 MutexGuard 更严格,因为当你调用 with_state 时,守卫保证在关闭 returns 之后被删除。另一方面,返回 MutexGuard 更通用,因为您可以根据 returns 守卫的函数编写 with_state,但不能反过来(使用with_state写一个函数,returns一个守卫)。