Как я могу передать ссылку на переменную стека в поток?
Я пишу сервер WebSocket, к которому подключается веб-клиент, чтобы играть в шахматы против многопоточного компьютерного ИИ. Сервер WebSocket хочет передать Logger
объект в код AI. Logger
объект будет передавать по конвейеру строки журнала от ИИ к веб-клиенту. 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
когда он находится внутри библиотеки, код распараллелен. Это особенно раздражает, потому что время жизни соединения гарантированно будет больше, чем время жизни рабочих потоков.
Я что-то пропустил?
1 ответ
Поддержка потоков в стандартной библиотеке позволяет созданным потокам пережить созданный ими поток; это хорошая вещь! Однако, если вы передадите ссылку на переменную, выделенную в стеке, одному из этих потоков, нет гарантии, что эта переменная будет действительной к моменту выполнения потока. На других языках это позволило бы потоку обращаться к недопустимой памяти, создавая кучу проблем безопасности памяти.
К счастью, мы не ограничены стандартной библиотекой. По крайней мере два ящика предоставляют потоки с областью видимости - потоки, которые гарантированно завершат работу до окончания определенной области. Это может гарантировать, что переменные стека будут доступны на протяжении всего потока:
Существуют также ящики, которые абстрагируют низкоуровневые детали "потоков", но позволяют вам достичь ваших целей:
Вот примеры каждого. Каждый пример порождает несколько потоков и мутирует локальный вектор на месте без блокировки, нет Arc
и нет клонирования. Обратите внимание, что мутация имеет sleep
Позвоните, чтобы убедиться, что звонки происходят параллельно.
Вы можете расширить примеры, чтобы поделиться ссылкой на любой тип, который реализует Sync
такой как Mutex
или Atomic*
, Однако их использование может привести к блокировке.
область видимости-ThreadPool
extern crate scoped_threadpool;
use scoped_threadpool::Pool;
use std::thread;
use std::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_millis(1000));
*e += 1;
});
}
});
println!("{:?}", vec);
}
коромысло
extern crate crossbeam;
use std::thread;
use std::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_millis(1000));
*e += 1;
});
}
});
println!("{:?}", vec);
}
вискоза
extern crate rayon;
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
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_millis(1000));
*e += 1;
});
println!("{:?}", vec);
}
клиент должен установить соединение в
Arc
когда он является внутренним для библиотеки, код распараллелен
Возможно, вы можете скрыть свой параллелизм лучше, чем? Не могли бы вы принять регистратор, а затем обернуть его в Arc
/ Mutex
прежде чем передать его в ваши темы?