我如何传递一个参考堆栈变量的线程?

问题描述 投票:15回答:1

我在写,其中一个网络客户端连接到下棋对多线程的电脑AI一WebSocket的服务器。 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连接时,它内部的代码并行库。因为连接的寿命保证是比工作线程的寿命更高,这是特别烦人。

我错过了什么?

rust
1个回答
22
投票

在标准库中的线程支持允许创建的线程活得比创建它们的线程;这是好事!但是,如果你是一个堆栈分配的变量的引用传递给这些线程中的一个,但也不能保证该变量仍然将是由线程执行时有效。在其他语言中,这将允许线程访问无效内存,造就了一堆内存安全问题。

幸运的是,我们不会仅限于标准库。至少两个板条箱提供作用域线程 - 即保证退出一定范围结束之前线程。这些可以确保堆栈变量将可用于线程的整个过程:

也有包装箱的是抽象掉“线程”的低层次的细节,但让你实现自己的目标:

以下是各的例子。各实施例产生一个数量的线程和变异代替局部矢量没有锁定,无Arc,没有克隆。需要注意的是突变有sleep电话,以帮助验证调用并行发生的。

可以延长例子分享给它实现Sync,诸如MutexAtomic*任何类型的引用。使用这些不过会引入锁定。

scoped-threadpool

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

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

客户需要到框在Arc连接时,它是内部的,该代码被并行库

也许你可以隐藏你的并行性更好呢?你能接受的记录,然后交给受你的线程之前将它包装在一个Arc / Mutex

© www.soinside.com 2019 - 2024. All rights reserved.