如何将对堆栈变量的引用传递给线程?

如何将对堆栈变量的引用传递给线程?

我正在编写一个WebSocket服务器,其中一个Web客户端连接到一个多线程计算机AI上下棋。WebSocket服务器希望传递一个Logger对象进入人工智能代码。这个Logger对象将把日志行从AI排到Web客户端。这个Logger必须包含对客户端连接的引用。

我对生命如何与线程交互感到困惑。我用一个Wrapper由类型参数化的结构。这个run_thread函数尝试展开该值并将其记录下来。

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的堆栈框架,即使在堆栈帧结束之前线程将被连接。我可以从堆栈中复制值:

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是对一个我不想复制的大对象的引用:

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


在我真正的程序中,看起来两个Logger连接对象必须放置在Arc包装纸。客户端被要求将连接放在Arc当代码被并行化时,代码是库内部的。这尤其令人讨厌,因为连接的生存期肯定大于工作线程的生存期。

我错过了什么吗?


拉丁的传说
浏览 557回答 2
2回答

隔江千里

标准库中的线程支持允许创建的线程比创建线程的线程更长;这是一件好事!但是,如果要将对堆栈分配变量的引用传递给这些线程之一,则无法保证该变量在线程执行时仍然有效。在其他语言中,这将允许线程访问无效内存,从而产生大量内存安全问题。幸运的是,我们并不局限于标准库。至少有两个箱子作用域螺纹-保证在某个作用域结束之前退出的线程。这可以确保堆栈变量在线程的整个期间都可用:横梁范围-线程池还有一些板条箱可以抽象出“线程”的底层细节,但允许您完成目标:人造丝下面是每一个例子。每个示例生成大量线程,并在没有锁定、没有锁定的情况下对本地向量进行变异。Arc也没有克隆。请注意,该突变具有sleep调用以帮助验证这些呼叫是并行进行的。您可以扩展这些示例,以共享对实现的任何类型的引用。Sync,如Mutex或者Atomic*..然而,使用这些将引入锁定。范围-线程池use&nbsp;scoped_threadpool::Pool;&nbsp;//&nbsp;0.1.9use&nbsp;std::{thread,&nbsp;time::Duration};fn&nbsp;main()&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;mut&nbsp;vec&nbsp;=&nbsp;vec![1,&nbsp;2,&nbsp;3,&nbsp;4,&nbsp;5]; &nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;mut&nbsp;pool&nbsp;=&nbsp;Pool::new(vec.len()&nbsp;as&nbsp;u32); &nbsp;&nbsp;&nbsp;&nbsp;pool.scoped(|scoped|&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;e&nbsp;in&nbsp;&mut&nbsp;vec&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scoped.execute(move&nbsp;||&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;thread::sleep(Duration::from_secs(1)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*e&nbsp;+=&nbsp;1; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;}); &nbsp;&nbsp;&nbsp;&nbsp;println!("{:?}",&nbsp;vec);}横梁use&nbsp;crossbeam;&nbsp;//&nbsp;0.6.0use&nbsp;std::{thread,&nbsp;time::Duration};fn&nbsp;main()&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;mut&nbsp;vec&nbsp;=&nbsp;vec![1,&nbsp;2,&nbsp;3,&nbsp;4,&nbsp;5]; &nbsp;&nbsp;&nbsp;&nbsp;crossbeam::scope(|scope|&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;e&nbsp;in&nbsp;&mut&nbsp;vec&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scope.spawn(move&nbsp;|_|&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;thread::sleep(Duration::from_secs(1)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*e&nbsp;+=&nbsp;1; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;}) &nbsp;&nbsp;&nbsp;&nbsp;.expect("A&nbsp;child&nbsp;thread&nbsp;panicked"); &nbsp;&nbsp;&nbsp;&nbsp;println!("{:?}",&nbsp;vec);}人造丝use&nbsp;rayon::iter::{IntoParallelRefMutIterator,&nbsp;ParallelIterator};&nbsp;//&nbsp;1.0.3use&nbsp;std::{thread,&nbsp;time::Duration};fn&nbsp;main()&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;mut&nbsp;vec&nbsp;=&nbsp;vec![1,&nbsp;2,&nbsp;3,&nbsp;4,&nbsp;5]; &nbsp;&nbsp;&nbsp;&nbsp;vec.par_iter_mut().for_each(|e|&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;thread::sleep(Duration::from_secs(1)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*e&nbsp;+=&nbsp;1; &nbsp;&nbsp;&nbsp;&nbsp;}); &nbsp;&nbsp;&nbsp;&nbsp;println!("{:?}",&nbsp;vec);}客户端必须将连接放在Arc当代码被并行化时,代码是库内部的。也许你能更好地隐藏你的并行性?您能否接受记录器,然后将其包装在Arc&nbsp;/&nbsp;Mutex在把它交给你的线程之前?

慕森王

我的解决办法是Logger实施Clone,并有一个类型为Arc<Mutex<Connection>>..然后,用户可以将记录器的一个克隆传递给线程代码。用户无法转移Connection对于线程代码(用户需要它用于其他目的),因此我认为线程代码不可能方便地执行Arc并代表用户进行装箱。&nbsp;
打开App,查看更多内容
随时随地看视频慕课网APP