8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

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

user3728644 1月前

29 0

我正在编写一个 WebSocket 服务器,Web 客户端连接到该服务器,与多线程计算机 AI 下棋。WebSocket 服务器想要将 Logger 对象传递到 AI 代码中。Logger 对象是

我正在编写一个 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 。这尤其令人讨厌,因为连接的生命周期保证大于工作线程的生命周期。

我是否遗漏了什么?

帖子版权声明 1、本帖标题:如何将堆栈变量的引用传递给线程?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由user3728644在本站《multithreading》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 标准库中的基本线程支持允许创建的线程比创建它们的线程存活更久;这是一件好事!但是,如果您将对堆栈分配变量的引用传递给其中一个线程,则无法保证该变量在线程执行时仍然有效。在其他语言中,这将允许线程访问无效内存,从而产生一堆内存安全问题。

    一个解决方案是 使用作用域线程 — 保证在父线程退出之前退出的线程。这可以确保父线程中的堆栈变量在线程的整个持续时间内可用。

    Rust 1.63

    std::thread::scope 移除 回归 , 重返稳定 Rust

    use std::{thread, time::Duration};
    
    fn main() {
        let mut vec = vec![1, 2, 3, 4, 5];
    
        thread::scope(|scope| {
            for e in &mut vec {
                scope.spawn(move || {
                    thread::sleep(Duration::from_secs(1));
                    *e += 1;
                });
            }
        });
    
        println!("{:?}", vec);
    }
    

    早期的 Rust 版本或当你需要更多控制时

    横梁

    我们不局限于标准库;一个流行的作用域线程包是 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 和没有克隆的情况下就地改变本地向量。请注意,改变有一个 sleep 调用,以帮助验证调用是否并行发生。

    您可以扩展示例以共享对任何实现 Sync ,例如 a Mutex 或 an Atomic* 。但是,使用这些会引入锁定。


    当代码在库内部并行化时, Arc 客户端需要将连接装箱

    那么,也许您可​​以更好地隐藏并行性?您可以接受记录器,然后在 Arc <将其传递给线程之前code>/ Mutex /

  • 非常感谢您的回复!我的解决方案是让 Logger 实现 Clone,并添加一个类型为 Arc 的字段>。然后,用户可以将记录器的克隆传递给线程代码。用户无法将连接的所有权转让给线程代码(用户需要它用于其他目的),因此我认为线程代码不可能方便地代表用户执行 Arc 和装箱。

  • Keks 1月前 0 只看Ta
    引用 4

    如果你尝试传递的变量无法实现 Clone/Copy,你会怎么做?就像来自 rusb crate 的 USB 设备句柄一样

  • cefn 1月前 0 只看Ta
    引用 5

    @BrandonRos Vec 未实现 Copy,并且这些代码示例中未使用 Clone。此处提供的代码适用于此类类型。

返回
作者最近主题: