如何将堆栈变量的引用传递给线程? [英] How can I pass a reference to a stack variable to a thread?

查看:26
本文介绍了如何将堆栈变量的引用传递给线程?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个 WebSocket 服务器,其中一个 Web 客户端连接以与多线程计算机 AI 下棋.WebSocket 服务器希望将 Logger 对象传递到 AI 代码中.Logger 对象将通过管道将日志行从 AI 传送到 Web 客户端.Logger 必须包含对客户端连接的引用.

我对生命周期如何与线程交互感到困惑.我已经用由类型参数化的 Wrapper 结构重现了这个问题.run_thread 函数尝试解开该值并记录它.

使用 std::fmt::Debug;使用 std::thread;结构包装器{价值:T,}fn run_thread(wrapper: Wrapper) {让 thr = thread::spawn(移动 || {println!("{:?}", wrapper.val);});thr.join();}fn 主(){run_thread(Wrapper::<i32> { val: -1 });}

wrapper 参数存在于堆栈中,它的生命周期不会超过 run_thread 的堆栈帧,即使线程将在堆栈帧之前加入结束.我可以从堆栈中复制值:

使用 std::fmt::Debug;使用 std::thread;struct Wrapper{价值:T,}fn run_thread(wrapper: Wrapper) {让 thr = thread::spawn(移动 || {println!("{:?}", wrapper.val);});thr.join();}fn 主(){run_thread(Wrapper::<i32> { val: -1 });}

如果 T 是对我不想复制的大对象的引用,这将不起作用:

使用 std::fmt::Debug;使用 std::thread;struct Wrapper{价值:T,}fn run_thread(wrapper: Wrapper) {让 thr = thread::spawn(移动 || {println!("{:?}", wrapper.val);});thr.join();}fn 主(){让 mut v = Vec::new();对于我在 0..1000 {v.push(i);}run_thread(Wrapper { val: &v });}

结果:

错误:`v` 的寿命不够长-->src/main.rs:22:32|22 |run_thread(Wrapper { val: &v });|^ 活得不够久23 |}|- 借来的价值只存在到这里|= 注意:借用的值必须在静态生命周期内有效...

我能想到的唯一解决方案是使用Arc.

使用 std::fmt::Debug;使用 std::sync::Arc;使用 std::thread;struct Wrapper{arc_val:弧,}fn run_thread(wrapper: &Wrapper) {让 arc_val = wrapper.arc_val.clone();让 thr = thread::spawn(移动 || {println!("{:?}", *arc_val);});thr.join();}fn 主(){让 mut v = Vec::new();对于我在 0..1000 {v.push(i);}让 w = Wrapper { arc_val: Arc::new(v) };run_thread(&w);println!("{}", (*w.arc_val)[0]);}

在我的实际程序中,Logger 和连接对象似乎都必须放在 Arc 包装器中.当代码是并行化的库内部时,客户端需要将连接装箱在 Arc 中,这似乎很烦人.这特别烦人,因为连接的生命周期保证大于工作线程的生命周期.

我错过了什么吗?

解决方案

标准库中的线程支持允许创建的线程比创建它们的线程存活时间更长;这是好事!但是,如果您要将堆栈分配变量的引用传递给这些线程之一,则无法保证该变量在线程执行时仍然有效.在其他语言中,这将允许线程访问无效内存,从而产生一堆内存安全问题.

幸运的是,我们不仅限于标准库.至少有两个 crate 提供作用域线程——保证在特定作用域结束之前退出的线程.这些可以确保堆栈变量在线程的整个持续时间内都可用:

还有一些 crate 可以抽象出线程"的低级细节.但允许您实现目标:

以下是每个示例.每个示例都会产生多个线程并在没有锁定、没有 Arc 和克隆的情况下改变本地向量.请注意,突变有一个 sleep 调用,以帮助验证这些调用是否并行发生.

您可以扩展示例以共享对实现 Sync,例如 MutexAtomic*.但是,使用这些会引入锁定.

横梁

使用横梁;//0.6.0使用 std::{thread, time::Duration};fn 主(){让 mut vec = vec![1, 2, 3, 4, 5];横梁::范围(|范围| {对于 e in &mut vec {scope.spawn(移动 |_| {thread::sleep(Duration::from_secs(1));*e += 1;});}}).expect(一个子线程恐慌");println!("{:?}", vec);}

人造丝

use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};//1.0.3使用 std::{thread, time::Duration};fn 主(){让 mut vec = vec![1, 2, 3, 4, 5];vec.par_iter_mut().for_each(|e| {thread::sleep(Duration::from_secs(1));*e += 1;});println!("{:?}", vec);}

<块引用>

当代码并行化在库内部时,客户端需要将连接装箱在 Arc

也许你可以更好地隐藏你的并行性?您能否接受记录器,然后将其包装在 Arc/Mutex 中,然后再将其交给您的线程?

I'm writing a WebSocket server where a web client connects to play chess against a multithreaded computer AI. The WebSocket server wants to pass a Logger object into the AI code. The Logger object is going to pipe down log lines from the AI to the web client. The Logger must contain a reference to the client connection.

I'm confused about how lifetimes interact with threads. I've reproduced the problem with a Wrapper struct parameterized by a type. The run_thread function tries to unwrap the value and log it.

use std::fmt::Debug;
use std::thread;

struct Wrapper<T: Debug> {
    val: T,
}

fn run_thread<T: Debug>(wrapper: Wrapper<T>) {
    let thr = thread::spawn(move || {
        println!("{:?}", wrapper.val);
    });

    thr.join();
}

fn main() {
    run_thread(Wrapper::<i32> { val: -1 });
}

The wrapper argument lives on the stack, and its lifetime doesn't extend past run_thread's stack frame, even though the thread will be joined before the stack frame ends. I'd could copy the value off the stack:

use std::fmt::Debug;
use std::thread;

struct Wrapper<T: Debug + Send> {
    val: T,
}

fn run_thread<T: Debug + Send + 'static>(wrapper: Wrapper<T>) {
    let thr = thread::spawn(move || {
        println!("{:?}", wrapper.val);
    });

    thr.join();
}

fn main() {
    run_thread(Wrapper::<i32> { val: -1 });
}

This will not work if T is a reference to a big object I don't want copied:

use std::fmt::Debug;
use std::thread;

struct Wrapper<T: Debug + Send> {
    val: T,
}

fn run_thread<T: Debug + Send + 'static>(wrapper: Wrapper<T>) {
    let thr = thread::spawn(move || {
        println!("{:?}", wrapper.val);
    });

    thr.join();
}

fn main() {
    let mut v = Vec::new();
    for i in 0..1000 {
        v.push(i);
    }

    run_thread(Wrapper { val: &v });
}

Which results in:

error: `v` does not live long enough
  --> src/main.rs:22:32
   |
22 |     run_thread(Wrapper { val: &v });
   |                                ^ does not live long enough
23 | }
   | - borrowed value only lives until here
   |
   = note: borrowed value must be valid for the static lifetime...

The only solution I can think of is to use an Arc.

use std::fmt::Debug;
use std::sync::Arc;
use std::thread;

struct Wrapper<T: Debug + Send + Sync + 'static> {
    arc_val: Arc<T>,
}

fn run_thread<T: Debug + Send + Sync + 'static>(wrapper: &Wrapper<T>) {
    let arc_val = wrapper.arc_val.clone();
    let thr = thread::spawn(move || {
        println!("{:?}", *arc_val);
    });

    thr.join();
}

fn main() {
    let mut v = Vec::new();
    for i in 0..1000 {
        v.push(i);
    }

    let w = Wrapper { arc_val: Arc::new(v) };
    run_thread(&w);

    println!("{}", (*w.arc_val)[0]);
}

In my real program, it appears that both the Logger and the connection object must be placed in Arc wrappers. It seems annoying that the client is required to box the connection in an Arc when it is internal to the library that the code is parallelized. This is especially annoying because the lifetime of the connection is guaranteed to be greater than the lifetime of the worker threads.

Have I missed something?

解决方案

The thread support in the standard library allows the created threads to outlive the thread that created them; that's a good thing! However, if you were to pass a reference to a stack-allocated variable to one of these threads, there's no guarantee that the variable will still be valid by the time the thread executes. In other languages, this would allow the thread to access invalid memory, creating a pile of memory safety issues.

Fortunately, we aren't limited to the standard library. At least two crates provide scoped threads — threads that are guaranteed to exit before a certain scope ends. These can ensure that stack variables will be available for the entire duration of the thread:

There are also crates that abstract away the low-level details of "threads" but allow you to accomplish your goals:

Here are examples of each. Each example spawns a number of threads and mutates a local vector in place with no locking, no Arc, and no cloning. Note that the mutation has a sleep call to help verify that the calls are happening in parallel.

You can extend the examples to share a reference to any type which implements Sync, such as a Mutex or an Atomic*. Using these would introduce locking, however.

crossbeam

use crossbeam; // 0.6.0
use std::{thread, time::Duration};

fn main() {
    let mut vec = vec![1, 2, 3, 4, 5];

    crossbeam::scope(|scope| {
        for e in &mut vec {
            scope.spawn(move |_| {
                thread::sleep(Duration::from_secs(1));
                *e += 1;
            });
        }
    })
    .expect("A child thread panicked");

    println!("{:?}", vec);
}

rayon

use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; // 1.0.3
use std::{thread, time::Duration};

fn main() {
    let mut vec = vec![1, 2, 3, 4, 5];

    vec.par_iter_mut().for_each(|e| {
        thread::sleep(Duration::from_secs(1));
        *e += 1;
    });

    println!("{:?}", vec);
}

the client is required to box the connection in an Arc when it is internal to the library that the code is parallelized

Perhaps you can hide your parallelism better then? Could you accept the logger and then wrap it in an Arc / Mutex before handing it off to your threads?

这篇关于如何将堆栈变量的引用传递给线程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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