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

标签 multithreading rust reference lifetime

我正在写一个WebSocket服务器,其中一个Web客户端连接到多线程计算机AI来下棋。 WebSocket服务器希望将Logger对象传递到AI代码中。 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...

我能想到的唯一解决方案是使用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中对连接进行装箱。这特别令人讨厌,因为保证了连接的生存期大于工作线程的生存期。

我错过了什么吗?

最佳答案

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

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

  • crossbeam
  • scoped-threadpool

  • 也有一些 crate 可以抽象出“线程”的低级详细信息,但可以实现您的目标:
  • rayon

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

    您可以扩展示例以共享对实现 Sync 的任何类型的引用,例如MutexAtomic*。但是,使用这些将导致锁定。

    范围线程池
    use scoped_threadpool::Pool; // 0.1.9
    use std::{thread, time::Duration};
    
    fn main() {
        let mut vec = vec![1, 2, 3, 4, 5];
        let mut pool = Pool::new(vec.len() as u32);
    
        pool.scoped(|scoped| {
            for e in &mut vec {
                scoped.execute(move || {
                    thread::sleep(Duration::from_secs(1));
                    *e += 1;
                });
            }
        });
    
        println!("{:?}", vec);
    }
    

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

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



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

    关于multithreading - 如何将对堆栈变量的引用传递给线程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63851730/

    相关文章:

    Java列表实例在复制后保留其指针

    c#关于接口(interface)

    java - 可调用任务未正确执行

    python - python 中的线程 exit_request

    rust - Vec<&&str> 与 Vec<&Str> 相同吗?

    rust - 无法找到不安全的特征实现

    rust - 使用 rustc 编译代码时如何指定要使用的版本?

    c++ - "undefined reference"(适用于 linux,但不适用于 Windows 上的 cygwin)

    c++ - boost::asio: "strand"类型的同步原语有什么名字吗?

    java - run 和 equals 方法有奇怪的事情或者我做的任何错误