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

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

问题描述

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

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.

我对生存期如何与线程交互感到困惑.我已经用类型指定参数的 Wrapper 结构重现了该问题. run_thread 函数尝试解开该值并将其记录下来.

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 });
}

包装器参数 wrapper 存在,并且其寿命不会超过 run_thread 的堆栈框架,即使线程将在堆栈框架之前连接结束.我可以从堆栈中复制值:

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 });
}

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

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 });
}

这将导致:

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...

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

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]);
}

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

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.

我错过了什么吗?

推荐答案

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

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:

这里是每个示例.每个示例都生成许多线程,并在没有锁定,没有 Arc 和克隆的情况下对本地向量进行了变异.请注意,该变异具有 sleep 调用,以帮助验证调用是否并行发生.

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.

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

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.

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);
}

人造丝

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);
}

当库在内部对代码进行并行化时,要求客户端将连接放在 Arc

也许您可以更好地隐藏并行性?您可以接受记录器,然后将其包装到 Arc / Mutex 中,然后再传递给线程吗?

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天全站免登陆