确保 MutexGuard 作为引用的扩展生命周期。

huangapple go评论93阅读模式
英文:

Ensure extended lifetime of MutexGuard used as reference

问题

在下面的最小代码示例中,使用MutexGuard来访问一个BindGroup,该BindGroup的存在时间肯定比RenderPass的生命周期长。然而,这显然对Rust编译器是未知的,导致了生命周期错误。

背景信息:RenderPassBindGroup都来自于wgpu crate。因此,我无法简单地调整它们的方法参数。
RenderPass在每一帧都会创建,而TextureManager只在程序启动时创建。
TextureManager将对BindGroup的引用保持为Arc<Mutex<...>>,以便从多个线程中进行操作。

use core::marker::PhantomData;
use std::ops::Deref;
use std::sync::{Arc, Mutex};

fn main() {
    let texture_manager = TextureManager {
        active_bind_group: Arc::new(Mutex::new(None)),
    };
    texture_manager.activate_texture(0);

    let mut render_pass = RenderPass::new();

    step(&mut render_pass, &texture_manager);
}

fn step<'pass>(render_pass: &mut RenderPass<'pass>, texture_manager: &'pass TextureManager) {
    let guard = texture_manager.active_bind_group.lock().unwrap();
    if let Some(bind_group) = guard.deref() {
        render_pass.set_bind_group(bind_group);
    }
}

struct TextureManager {
    active_bind_group: Arc<Mutex<Option<BindGroup>>>,
    // textures: Arc<Mutex<Vec<Texture>>>,
}

impl TextureManager {
    // Only uses &self instead of &mut self for providing immutable interface
    // so that a `Arc<TextureManager>` suffices for use in multithreaded code
    fn activate_texture(&self, index: usize) {
        // let texture = textures.lock().unwrap()[index];
        // update bind group using the texture
        *self.active_bind_group.lock().unwrap() = Some(BindGroup);
    }
}

struct BindGroup;

struct RenderPass<'pass> {
    phantom: PhantomData<&'pass ()>,
}

impl<'pass> RenderPass<'pass> {
    fn new() -> Self {
        Self {
            phantom: PhantomData,
        }
    }

    fn set_bind_group(&mut self, bind_group: &'pass BindGroup) {}
}

Rust Playground

产生的错误:

error[E0597]: `guard` does not live long enough
  --> src/main.rs:18:31
   |
16 | fn step<'pass>(render_pass: &mut RenderPass<'pass>, texture_manager: &'pass TextureManager) {
   |         ----- lifetime `'pass` defined here
17 |     let guard = texture_manager.active_bind_group.lock().unwrap();
   |         ----- binding `guard` declared here
18 |     if let Some(bind_group) = guard.deref() {
   |                               ^^^^^^^^^^^^^
   |                               |
   |                               borrowed value does not live long enough
   |                               argument requires that `guard` is borrowed for `'pass`
...
21 | }
   | - `guard` dropped here while still borrowed

我理解错误的起源:MutexGuard仅在step方法的作用域中存在。一旦它被丢弃,就无法再保证对原始值的引用。
尽管如此,我还没有找到解决这个问题的任何解决方案。
在这种情况下,是否有可能使用不同的构造而不是Mutex

英文:

In the following minimal code example, a MutexGuard is used for accessing a BindGroup that is sure to exist longer than the lifetime of the RenderPass. However, this is obviously not known to the Rust compiler, resulting in a lifetime error.

For context: both RenderPass and BindGroup stem from the wgpu crate. Thus, it is not possible for me to simply adjust their method parameters.
The RenderPass is created every frame, whereas the TextureManager is only created once the program starts.
The TextureManager keeps the reference to the BindGroup as Arc&lt;Mutex&lt;…&gt;&gt; in order to manipulate it from multiple threads.

use core::marker::PhantomData;
use std::ops::Deref;
use std::sync::{Arc, Mutex};

fn main() {
    let texture_manager = TextureManager {
        active_bind_group: Arc::new(Mutex::new(None)),
    };
    texture_manager.activate_texture(0);

    let mut render_pass = RenderPass::new();

    step(&amp;mut render_pass, &amp;texture_manager);
}

fn step&lt;&#39;pass&gt;(render_pass: &amp;mut RenderPass&lt;&#39;pass&gt;, texture_manager: &amp;&#39;pass TextureManager) {
    let guard = texture_manager.active_bind_group.lock().unwrap();
    if let Some(bind_group) = guard.deref() {
        render_pass.set_bind_group(bind_group);
    }
}

struct TextureManager {
    active_bind_group: Arc&lt;Mutex&lt;Option&lt;BindGroup&gt;&gt;&gt;,
    // textures: Arc&lt;Mutex&lt;Vec&lt;Texture&gt;&gt;&gt;,
}

impl TextureManager {
    // Only uses &amp;self instead of &amp;mut self for providing immutable interface
    // so that a `Arc&lt;TextureManager&gt;` suffices for use in multithreaded code
    fn activate_texture(&amp;self, index: usize) {
        // let texture = textures.lock().unwrap()[index];
        // update bind group using the texture
        *self.active_bind_group.lock().unwrap() = Some(BindGroup);
    }
}

struct BindGroup;

struct RenderPass&lt;&#39;pass&gt; {
    phantom: PhantomData&lt;&amp;&#39;pass ()&gt;,
}

impl&lt;&#39;pass&gt; RenderPass&lt;&#39;pass&gt; {
    fn new() -&gt; Self {
        Self {
            phantom: PhantomData,
        }
    }

    fn set_bind_group(&amp;mut self, bind_group: &amp;&#39;pass BindGroup) {}
}

Rust Playground

The resulting error:

error[E0597]: `guard` does not live long enough
  --&gt; src/main.rs:18:31
   |
16 | fn step&lt;&#39;pass&gt;(render_pass: &amp;mut RenderPass&lt;&#39;pass&gt;, texture_manager: &amp;&#39;pass TextureManager) {
   |         ----- lifetime `&#39;pass` defined here
17 |     let guard = texture_manager.active_bind_group.lock().unwrap();
   |         ----- binding `guard` declared here
18 |     if let Some(bind_group) = guard.deref() {
   |                               ^^^^^^^^^^^^^
   |                               |
   |                               borrowed value does not live long enough
   |                               argument requires that `guard` is borrowed for `&#39;pass`
...
21 | }
   | - `guard` dropped here while still borrowed

I understand the origin of the error: the MutexGuard only lives for the scope of the step method. As soon as it is dropped, a reference to the original value can no longer be ensured.
Still, I haven't found any solution as to how to solve this problem.
Are there potentially different constructs than Mutex to use in this scenario?

答案1

得分: 2

基本上,你所面临的问题不仅仅是写下正确的生命周期;你还必须向编译器证明在渲染过程中使用的绑定组不会被丢弃或改变。

一种方法是将MutexGuard移动到与RenderPass相同的作用域中(而不是step),以确保其寿命足够长。这基本上意味着将step()内联到main()中。

另一种更具组合性的方法是利用共享所有权。将BindGroup放入Arc中,并进行克隆,这样即使互斥锁中的值发生了改变,旧值仍然可用于渲染过程,不会被丢弃且不可变。然而,你仍然需要一个地方来存储克隆的Arc,以在所需的时间段内保持其有效。一种简单高效的方法是在创建渲染过程之前设置要使用的绑定组的某个变量:

fn main() {
    let texture_manager = TextureManager {
        active_bind_group: Arc::new(Mutex::new(None)),
    };
    texture_manager.activate_texture(0);

    let bind_group_to_use = get_bind_group(&texture_manager); // before pass
    let mut render_pass = RenderPass::new();
    if let Some(g) = bind_group_to_use {                      // within pass
        render_pass.set_bind_group(&g);
    }
}

/// This is what used to be the step() function
fn get_bind_group(texture_manager: &TextureManager) -> Option<Arc<BindGroup>> {
    Option::clone(&texture_manager.active_bind_group.lock().unwrap())
}

struct TextureManager {
    active_bind_group: Arc<Mutex<Option<Arc<BindGroup>>>>,
}

impl TextureManager {
    fn activate_texture(&self, index: usize) {
        // creates the Arc
        *self.active_bind_group.lock().unwrap() = Some(Arc::new(BindGroup));
    }
}

Playground

但也许这并不适用于你。也许你实际上需要在构建RenderPass时可能创建多个绑定组。在这种情况下,typed_arena::Arena可以帮助你,它提供了一个地方来存储可借用的事物,即使其中一些在你开始借用其他事物时并不存在,它们都具有相同的生命周期。

使用arena的缺点是arena必须为其元素分配内存。

use typed_arena::Arena;

pub fn main() {
    let texture_manager = TextureManager {
        active_bind_group: Arc::new(Mutex::new(None)),
    };
    texture_manager.activate_texture(0);

    let arena = Arena::new();
    let mut render_pass = RenderPass::new();

    step(&arena, &mut render_pass, &texture_manager);
}

fn step<'mutex: 'pass, 'pass>(
    arena: &'pass Arena<MutexGuard<'mutex, Option<BindGroup>>>,
    render_pass: &mut RenderPass<'pass>,
    texture_manager: &'mutex TextureManager,
) {
    let guard = texture_manager.active_bind_group.lock().unwrap();
    let guard = arena.alloc(guard);
    if let Some(bind_group) = &**guard {
        render_pass.set_bind_group(bind_group);
    }
}

你还可以将上述两种方法结合起来,将Arc<BindGroup>存储在Arena中而不是MutexGuard中。这将提供更大的灵活性,你可以使用来自任何源的渲染过程(只要它们是Arc),并且arena在其类型中不需要'mutex生命周期。

英文:

Fundamentally, what you have here is not just a matter of writing down the right lifetime; you have to demonstrate to the compiler that the bind group you're borrowing will not be dropped or mutated while it is in use by the render pass.

One way to do this is to move the MutexGuard into the same scope as the RenderPass (instead of step) so that it is known to live long enough. This basically means inlining step() into main().

Another way, that is more composable, is to make use of shared ownership. Put the BindGroup into an Arc, and clone it, so that even if the value in the mutex is changed, the old value is still available — immutable and not dropped — to the render pass. However, you'll still need a place to stash the cloned Arc to hold it alive for the desired period. The straightforward and efficient way to do this is to set up the bind groups you want to use in some variable before creating the render pass:

fn main() {
    let texture_manager = TextureManager {
        active_bind_group: Arc::new(Mutex::new(None)),
    };
    texture_manager.activate_texture(0);

    let bind_group_to_use = get_bind_group(&amp;texture_manager); // before pass
    let mut render_pass = RenderPass::new();
    if let Some(g) = bind_group_to_use {                      // within pass
        render_pass.set_bind_group(&amp;g);
    }
}

/// This is what used to be the step() function
fn get_bind_group(texture_manager: &amp;TextureManager) -&gt; Option&lt;Arc&lt;BindGroup&gt;&gt; {
    Option::clone(&amp;texture_manager.active_bind_group.lock().unwrap())
}

struct TextureManager {
    active_bind_group: Arc&lt;Mutex&lt;Option&lt;Arc&lt;BindGroup&gt;&gt;&gt;&gt;,
}

impl TextureManager {
    fn activate_texture(&amp;self, index: usize) {
        // creates the Arc
        *self.active_bind_group.lock().unwrap() = Some(Arc::new(BindGroup));
    }
}

Playground

But perhaps this won't do. Perhaps you actually need to potentially create many bind groups, and do it while you're building the RenderPass. In that case, typed_arena::Arena can help you — it gives you a place to stash borrowable things that all have the same lifetime even if some of them didn't exist at the time you start borrowing other ones.

The disadvantage of using an arena is that the arena must allocate memory for its elements.

use typed_arena::Arena;

pub fn main() {
    let texture_manager = TextureManager {
        active_bind_group: Arc::new(Mutex::new(None)),
    };
    texture_manager.activate_texture(0);

    let arena = Arena::new();
    let mut render_pass = RenderPass::new();

    step(&amp;arena, &amp;mut render_pass, &amp;texture_manager);
}

fn step&lt;&#39;mutex: &#39;pass, &#39;pass&gt;(
    arena: &amp;&#39;pass Arena&lt;MutexGuard&lt;&#39;mutex, Option&lt;BindGroup&gt;&gt;&gt;,
    render_pass: &amp;mut RenderPass&lt;&#39;pass&gt;,
    texture_manager: &amp;&#39;mutex TextureManager,
) {
    let guard = texture_manager.active_bind_group.lock().unwrap();
    let guard = arena.alloc(guard);
    if let Some(bind_group) = &amp;**guard {
        render_pass.set_bind_group(bind_group);
    }
}

You could also combine both of the above, by storing Arc&lt;BindGroup&gt;s in the Arena instead of MutexGuards. This would give even more flexibility — you can use render passes from any source (as long as they are Arced), and the arena won't have the &#39;mutex lifetime in its type.

huangapple
  • 本文由 发表于 2023年8月9日 17:08:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/76866204.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定