如何修复`self`在此处逃离方法体?

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

How to fix `self` escapes the method body here?

问题

我们想要传递给 thread::spawn 的数据,它强制具有 'static 生命周期,这会引发错误:

borrowed data escapes outside of method [E0521]
`self` escapes the method body here

Note: requirement occurs because of the type `EventLoopGroupSchedulerWorkerThreadData<'_>`, which makes the generic argument `'_` invariant

Note: the struct `EventLoopGroupSchedulerWorkerThreadData<'event_loop_group_scheduler>` is invariant over the parameter `'event_loop_group_scheduler`

Help: see <https://doc.rust-lang.org/nomicon/subtyping.html>; for more information about variance

我们可以确定,我们传递给 thread::spawn 的数据将具有与 'event_loop_group_scheduler 相同的生命周期,因为 EventLoopGroupSchedulerDrop 实现不断唤醒潜在休眠(等待任务出现时)的事件循环线程,直到它们都达到事件循环代码中检查我们是否没有任务且已调用 Drop 的点,并且中断事件循环并完成执行。

稳定的 Rust 不允许我们在没有 'static 要求或 scope(阻塞当前线程,直到子线程完成执行,但我们不想要)的情况下传递数据给 thread::spawn,但我们不想要这样严格的要求,特别是因为这种严格的要求通常会传播。


以下是一个可复现的示例:

struct EventLoopGroupScheduler<'event_loop_group_scheduler> {
    thread_count: usize,
    worker_thread_data_arc:
        Arc<EventLoopGroupSchedulerWorkerThreadData<'event_loop_group_scheduler>>,
    // 我们有 `worker_thread_data_ptr` 以优化对 `worker_thread_data_arc` 中数据的访问
    // 在热路径中
    worker_thread_data_ptr:
        *const EventLoopGroupSchedulerWorkerThreadData<'event_loop_group_scheduler>,
}

struct EventLoopGroupSchedulerWorkerThreadData<'event_loop_group_scheduler> {
    // 一些属性
    // ...
    action_queue_list: Vec<Mutex<VecDeque<Box<dyn FnOnce() + Send + 'event_loop_group_scheduler>>>>,
    // 一些属性
    // ...
}

impl<'event_loop_group_scheduler> EventLoopGroupScheduler<'event_loop_group_scheduler> {
    pub fn new(
        max_possible_concurrent_thread_count: usize,
        thread_count: usize,
        os_timer_precision: Duration,
    ) -> Self {
        let worker_thread_data_arc = Arc::new(EventLoopGroupSchedulerWorkerThreadData {
            // 一些属性
            // ...
            action_queue_list: (0..thread_count)
                .map(|_| Mutex::new(VecDeque::new()))
                .collect(),
            // 一些属性
            // ...
        });
        let worker_thread_data_ptr = Arc::into_raw(worker_thread_data_arc.clone());
        let event_loop_group_scheduler = Self {
            thread_count,
            worker_thread_data_arc,
            worker_thread_data_ptr,
        };

        event_loop_group_scheduler.start_event_loop_group();

        event_loop_group_scheduler
    }

    fn start_event_loop_group(&self) {
        for thread_index in 0..self.thread_count {
            let worker_thread_data_arc_clone = self.worker_thread_data_arc.clone();

            let _join_handle = thread::spawn(move || {
                // 这一行会引发错误:
                //
                // borrowed data escapes outside of method [E0521]
                //
                // `self` escapes the method body here
                //
                // Note: requirement occurs because of the type `EventLoopGroupSchedulerWorkerThreadData<'_>`, which makes the generic argument `'_` invariant
                //
                // Note: the struct `EventLoopGroupSchedulerWorkerThreadData<'event_loop_group_scheduler>` is invariant over the parameter `'event_loop_group_scheduler`
                //
                // Help: see <https://doc.rust-lang.org/nomicon/subtyping.html>; for more information about variance
                let worker_thread_data = worker_thread_data_arc_clone.as_ref();
            });
        }
    }
}
英文:

We want to pass to thread::spawn data, which it forces to have 'static lifetime, which causes error

borrowed data escapes outside of method [E0521]
`self` escapes the method body here

Note: requirement occurs because of the type `EventLoopGroupSchedulerWorkerThreadData<'_>`, which makes the generic argument `'_` invariant

Note: the struct `EventLoopGroupSchedulerWorkerThreadData<'event_loop_group_scheduler>` is invariant over the parameter `'event_loop_group_scheduler`

Help: see <https://doc.rust-lang.org/nomicon/subtyping.html>; for more information about variance

<hr>

We know for sure that data which we pass to thread::spawn will have same lifetime as &#39;event_loop_group_scheduler, because Drop implementation of EventLoopGroupScheduler continuously unparks potentially sleeping(while waiting for tasks to appear) event loop threads until all of them reach point in code in event loop which checks if we have no tasks and Drop was called, and break event loop and finish execution

Stable Rust doesn't let us pass data to thread::spawn without &#39;static requirement or scope(which blocks current thread until child thread finish execution, which we want to avoid), but we don't want to have such a strict requirement, especially since such strict requirement usually propagates

<hr>

Here is a reproducible example

struct EventLoopGroupScheduler&lt;&#39;event_loop_group_scheduler&gt; {
    thread_count: usize,
    worker_thread_data_arc:
        Arc&lt;EventLoopGroupSchedulerWorkerThreadData&lt;&#39;event_loop_group_scheduler&gt;&gt;,
    // We have `worker_thread_data_ptr` to optimize access to data in `worker_thread_data_arc`
    // in hot paths
    worker_thread_data_ptr:
        *const EventLoopGroupSchedulerWorkerThreadData&lt;&#39;event_loop_group_scheduler&gt;,
}

struct EventLoopGroupSchedulerWorkerThreadData&lt;&#39;event_loop_group_scheduler&gt; {
    // Some properties
    // ...
    action_queue_list: Vec&lt;Mutex&lt;VecDeque&lt;Box&lt;dyn FnOnce() + Send + &#39;event_loop_group_scheduler&gt;&gt;&gt;&gt;,
    // Some properties
    // ...
}

impl&lt;&#39;event_loop_group_scheduler&gt; EventLoopGroupScheduler&lt;&#39;event_loop_group_scheduler&gt; {
    pub fn new(
        max_possible_concurrent_thread_count: usize,
        thread_count: usize,
        os_timer_precision: Duration,
    ) -&gt; Self {
        let worker_thread_data_arc = Arc::new(EventLoopGroupSchedulerWorkerThreadData {
            // Some properties
            // ...
            action_queue_list: (0..thread_count)
                .map(|_| Mutex::new(VecDeque::new()))
                .collect(),
            // Some properties
            // ...
        });
        let worker_thread_data_ptr = Arc::into_raw(worker_thread_data_arc.clone());
        let event_loop_group_scheduler = Self {
            thread_count,
            worker_thread_data_arc,
            worker_thread_data_ptr,
        };

        event_loop_group_scheduler.start_event_loop_group();

        event_loop_group_scheduler
    }

    fn start_event_loop_group(&amp;self) {
        for thread_index in 0..self.thread_count {
            let worker_thread_data_arc_clone = self.worker_thread_data_arc.clone();

            let _join_handle = thread::spawn(move || {
                // This line causes error:
                //
                // borrowed data escapes outside of method [E0521]
                //
                // `self` escapes the method body here
                //
                // Note: requirement occurs because of the type `EventLoopGroupSchedulerWorkerThreadData&lt;&#39;_&gt;`, which makes the generic argument `&#39;_` invariant
                //
                // Note: the struct `EventLoopGroupSchedulerWorkerThreadData&lt;&#39;event_loop_group_scheduler&gt;` is invariant over the parameter `&#39;event_loop_group_scheduler`
                //
                // Help: see &lt;https://doc.rust-lang.org/nomicon/subtyping.html&gt;; for more information about variance
                let worker_thread_data = worker_thread_data_arc_clone.as_ref();
            });
        }
    }
}

答案1

得分: 3

你永远不能使用thread::spawn来处理借用数据,并且你对dyn FnOnce使用的生命周期意味着它们允许包含借用数据。

你的选择是:

  • 不允许借用数据;移除生命周期参数,并将dyn FnOnce的生命周期更改为'static。这可能是你需要做的。

  • 使用scoped threads。scope函数会阻塞直到所有线程终止,因此它们可以安全地使用借用数据。但是,这意味着你不能有一个允许线程在后台运行的“start”操作。

英文:

You can never use thread::spawn with borrowed data, and your use of a lifetime for your dyn FnOnces means that they're allowed to contain borrowed data.

Your choices are:

  • Don't allow borrowed data; remove the lifetime parameters and change the lifetime on the dyn FnOnce to &#39;static. This is probably what you will need to do.

  • Use scoped threads. The scope function blocks on all threads terminating, so they can safely use borrowed data. However, this means you can't have a “start” operation that lets the threads run in the background.

答案2

得分: 0

我原本想首先使用&#39;static,正如https://stackoverflow.com/a/76568430/9566462中建议的那样,但后来我想到,当我们知道EventLoopGroupSchedulerDrop实现会解锁线程,直到所有线程都完成执行时,&#39;static要求太严格,所以我们知道移动到thread::spawn的生命周期应该与&#39;event_loop_group_scheduler相同。

稳定的Rust不允许我们将非静态生命周期传递给thread::spawn而不等待所有线程,因此我们使用了这个不安全的extend_lifetime黑客(请注意,这是一种罕见的情况,只有在找不到其他可接受的解决方案时才有用,通常不建议使用,但也许您的情况类似,所以如果您找不到其他可接受的解决方案,可以继续阅读)。

struct EventLoopGroupScheduler&lt;&#39;event_loop_group_scheduler&gt; {
    thread_count: usize,
    unsafe_worker_thread_data:
        UnsafeCell&lt;EventLoopGroupSchedulerWorkerThreadData&lt;&#39;event_loop_group_scheduler&gt;&gt;,
}

struct EventLoopGroupSchedulerWorkerThreadData&lt;&#39;event_loop_group_scheduler&gt; {
    // 一些属性
    // ...
    action_queue_list: Vec&lt;Mutex&lt;VecDeque&lt;Box&lt;dyn FnOnce() + Send + &#39;event_loop_group_scheduler&gt;&gt;&gt;&gt;,
    // 一些属性
    // ...
}

unsafe fn extend_lifetime&lt;&#39;event_loop_group_scheduler&gt;(
    t: &amp;EventLoopGroupSchedulerWorkerThreadData&lt;&#39;event_loop_group_scheduler&gt;,
) -&gt; &amp;&#39;static EventLoopGroupSchedulerWorkerThreadData&lt;&#39;static&gt; {
    transmute::&lt;
        &amp;EventLoopGroupSchedulerWorkerThreadData&lt;&#39;event_loop_group_scheduler&gt;,
        &amp;EventLoopGroupSchedulerWorkerThreadData&lt;&#39;static&gt;,
    &gt;(t)
}

impl&lt;&#39;event_loop_group_scheduler&gt; EventLoopGroupScheduler&lt;&#39;event_loop_group_scheduler&gt; {
    pub fn new(
        max_possible_concurrent_thread_count: usize,
        thread_count: usize,
        os_timer_precision: Duration,
    ) -&gt; Self {
        let unsafe_worker_thread_data = UnsafeCell::new(EventLoopGroupSchedulerWorkerThreadData {
            // 一些属性
            // ...
            action_queue_list: (0..thread_count)
                .map(|_| Mutex::new(VecDeque::new()))
                .collect(),
            // 一些属性
            // ...
        });
        let event_loop_group_scheduler = Self {
            thread_count,
            unsafe_worker_thread_data,
        };

        event_loop_group_scheduler.start_event_loop_group();

        event_loop_group_scheduler
    }

    fn start_event_loop_group(&amp;self) {
        for thread_index in 0..self.thread_count {
            // 我们确信Drop会解锁线程,直到所有线程都到达它们决定最终中断循环并完成执行的代码位置,
            // 因此我们知道移动到`thread::spawn`的生命周期应与&#39;event_loop_group_scheduler相同
            //
            // 稳定的Rust不允许我们传递生命周期而不等待所有线程,
            // 因此我们使用了这个生命周期黑客
            let worker_thread_data =
                unsafe { extend_lifetime(&amp;*self.unsafe_worker_thread_data.get()) };

            let _join_handle = thread::spawn(move || {
                // 使用`worker_thread_data`
                let action_queue_mutex = unsafe {
                    worker_thread_data
                        .action_queue_list
                        .get_unchecked(thread_index)
                };

                // 一些代码
                // ...
            });

            // 一些代码
            // ...
        }
    }
}
英文:

I wanted to use &#39;static at first, as was suggested in https://stackoverflow.com/a/76568430/9566462, but then I thought that &#39;static is too strict requirement when we know that Drop implementation of EventLoopGroupScheduler will be unparking threads until all of them finish execution, so we know that moved to thread::spawn lifetime should be same as &#39;event_loop_group_scheduler

Stable Rust doesn't let us pass non-static lifetime to thread::spawn without waiting all threads, so we use this unsafe extend_lifetime hack(notice that it is a rare case when such solution is useful, and generally is not recommended, but maybe your case is similar, so read on if you didn't find other solutions acceptable)

struct EventLoopGroupScheduler&lt;&#39;event_loop_group_scheduler&gt; {
    thread_count: usize,
    unsafe_worker_thread_data:
        UnsafeCell&lt;EventLoopGroupSchedulerWorkerThreadData&lt;&#39;event_loop_group_scheduler&gt;&gt;,
}

struct EventLoopGroupSchedulerWorkerThreadData&lt;&#39;event_loop_group_scheduler&gt; {
    // Some properties
    // ...
    action_queue_list: Vec&lt;Mutex&lt;VecDeque&lt;Box&lt;dyn FnOnce() + Send + &#39;event_loop_group_scheduler&gt;&gt;&gt;&gt;,
    // Some properties
    // ...
}

unsafe fn extend_lifetime&lt;&#39;event_loop_group_scheduler&gt;(
    t: &amp;EventLoopGroupSchedulerWorkerThreadData&lt;&#39;event_loop_group_scheduler&gt;,
) -&gt; &amp;&#39;static EventLoopGroupSchedulerWorkerThreadData&lt;&#39;static&gt; {
    transmute::&lt;
        &amp;EventLoopGroupSchedulerWorkerThreadData&lt;&#39;event_loop_group_scheduler&gt;,
        &amp;EventLoopGroupSchedulerWorkerThreadData&lt;&#39;static&gt;,
    &gt;(t)
}

impl&lt;&#39;event_loop_group_scheduler&gt; EventLoopGroupScheduler&lt;&#39;event_loop_group_scheduler&gt; {
    pub fn new(
        max_possible_concurrent_thread_count: usize,
        thread_count: usize,
        os_timer_precision: Duration,
    ) -&gt; Self {
        let unsafe_worker_thread_data = UnsafeCell::new(EventLoopGroupSchedulerWorkerThreadData {
            // Some properties
            // ...
            action_queue_list: (0..thread_count)
                .map(|_| Mutex::new(VecDeque::new()))
                .collect(),
            // Some properties
            // ...
        });
        let event_loop_group_scheduler = Self {
            thread_count,
            unsafe_worker_thread_data,
        };

        event_loop_group_scheduler.start_event_loop_group();

        event_loop_group_scheduler
    }

    fn start_event_loop_group(&amp;self) {
        for thread_index in 0..self.thread_count {
            // We know for sure that Drop will be unparking threads until all
            // of them reach place in code where they will decide to finally break loop
            // and finish execution,
            // so we know that moved to `thread::spawn` lifetime should be same as &#39;event_loop_group_scheduler
            //
            // Stable Rust doesn&#39;t let us pass lifetime without waiting all threads,
            // so we use this lifetime hack
            let worker_thread_data =
                unsafe { extend_lifetime(&amp;*self.unsafe_worker_thread_data.get()) };

            let _join_handle = thread::spawn(move || {
                // Usage of `worker_thread_data`
                let action_queue_mutex = unsafe {
                    worker_thread_data
                        .action_queue_list
                        .get_unchecked(thread_index)
                };

                // Some code
                // ...
            });

            // Some code
            // ...
        }
    }
}

huangapple
  • 本文由 发表于 2023年6月27日 20:53:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/76565073.html
匿名

发表评论

匿名网友

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

确定