我正在编写一个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在代码并行化的库内部时将其连接起来.这尤其令人讨厌,因为保证连接的生命周期大于工作线程的生命周期.
我错过了什么吗?
She*_*ter 25
标准库中的线程支持允许创建的线程比创建它们的线程更长; 这是好事!但是,如果要将对堆栈分配的变量的引用传递给其中一个线程,则无法保证该变量在线程执行时仍然有效.在其他语言中,这将允许线程访问无效内存,从而产生一堆内存安全问题.
幸运的是,我们不仅限于标准库.至少有两个crates提供作用域线程 - 保证在某个作用域结束之前退出的线程.这些可以确保堆栈变量在整个线程持续时间内可用:
还有一些箱子可以抽象出"线程"的低级细节,但可以让你实现目标:
以下是各自的示例.每个示例都会生成许多线程,并在没有锁定,Arc没有克隆和没有克隆的情况下改变本地矢量.请注意,该突变有一个sleep调用来帮助验证调用是否并行发生.
您可以扩展示例以共享对任何实现类型的引用Sync,例如a Mutex或an Atomic*.然而,使用这些将引入锁定.
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);
}
当客户端
Arc在库内部并且代码被并行化时,客户端需要将连接打包
也许你可以更好地隐藏你的并行性?你可以接受记录器然后将它包装在一个Arc/ Mutex之前交给你的线程吗?
| 归档时间: | 
 | 
| 查看次数: | 3771 次 | 
| 最近记录: |