我在写,其中一个网络客户端连接到下棋对多线程的电脑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
连接时,它内部的代码并行库。因为连接的寿命保证是比工作线程的寿命更高,这是特别烦人。
我错过了什么?
在标准库中的线程支持允许创建的线程活得比创建它们的线程;这是好事!但是,如果你是一个堆栈分配的变量的引用传递给这些线程中的一个,但也不能保证该变量仍然将是由线程执行时有效。在其他语言中,这将允许线程访问无效内存,造就了一堆内存安全问题。
幸运的是,我们不会仅限于标准库。至少两个板条箱提供作用域线程 - 即保证退出一定范围结束之前线程。这些可以确保堆栈变量将可用于线程的整个过程:
也有包装箱的是抽象掉“线程”的低层次的细节,但让你实现自己的目标:
以下是各的例子。各实施例产生一个数量的线程和变异代替局部矢量没有锁定,无Arc
,没有克隆。需要注意的是突变有sleep
电话,以帮助验证调用并行发生的。
可以延长例子分享给它实现Sync
,诸如Mutex
或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
?