繁体   English   中英

如何将函数发送到另一个线程?

[英]How can I send a function to another thread?

我正在尝试为Rust项目编写一个更简单的单元测试运行器。 我创建了一个TestFixture特性,我的测试夹具结构将实现,类似于继承其他测试框架中的单元测试基类。 特点很简单。 这是我的测试夹具

pub trait TestFixture {
    fn setup(&mut self) -> () {}
    fn teardown(&mut self) -> () {}
    fn before_each(&mut self) -> () {}
    fn after_each(&mut self) -> () {}
    fn tests(&mut self) -> Vec<Box<Fn(&mut Self)>>
        where Self: Sized {
        Vec::new()
    }
}

我的测试运行功能如下

pub fn test_fixture_runner<T: TestFixture>(fixture: &mut T) {
    fixture.setup();

    let _r = fixture.tests().iter().map(|t| {
        let handle = thread::spawn(move || {
            fixture.before_each();
            t(fixture);
            fixture.after_each();
        });

        if let Err(_) = handle.join() {
            println!("Test failed!")
        } 
    });

    fixture.teardown();
}

我收到了错误

src/tests.rs:73:22: 73:35 error: the trait `core::marker::Send` is not implemented for the type `T` [E0277]
src/tests.rs:73         let handle = thread::spawn(move || {
                                     ^~~~~~~~~~~~~
note: in expansion of closure expansion
src/tests.rs:69:41: 84:6 note: expansion site
src/tests.rs:73:22: 73:35 note: `T` cannot be sent between threads safely
src/tests.rs:73         let handle = thread::spawn(move || {
                                     ^~~~~~~~~~~~~
note: in expansion of closure expansion
src/tests.rs:69:41: 84:6 note: expansion site
src/tests.rs:73:22: 73:35 error: the trait `core::marker::Sync` is not implemented for the type `for<'r> core::ops::Fn(&'r mut T)` [E0277]
src/tests.rs:73         let handle = thread::spawn(move || {
                                     ^~~~~~~~~~~~~
note: in expansion of closure expansion
src/tests.rs:69:41: 84:6 note: expansion site
src/tests.rs:73:22: 73:35 note: `for<'r> core::ops::Fn(&'r mut T)` cannot be shared between threads safely
src/tests.rs:73         let handle = thread::spawn(move || {
                                     ^~~~~~~~~~~~~
note: in expansion of closure expansion

我尝试在发送到线程的类型周围添加Arcs,没有骰子,同样的错误。

pub fn test_fixture_runner<T: TestFixture>(fixture: &mut T) {
    fixture.setup();

    let fix_arc = Arc::new(Mutex::new(fixture));
    let _r = fixture.tests().iter().map(|t| {
        let test_arc = Arc::new(Mutex::new(t));
        let fix_arc_clone = fix_arc.clone();
        let test_arc_clone = test_arc.clone();
        let handle = thread::spawn(move || {
            let thread_test = test_arc_clone.lock().unwrap();
            let thread_fix = fix_arc_clone.lock().unwrap();
            (*thread_fix).before_each();
            (*thread_test)(*thread_fix);
            (*thread_fix).after_each();
        });

        if let Err(_) = handle.join() {
            println!("Test failed!")
        } 
    });

    fixture.teardown();
}

样品测试夹具就像是

struct BuiltinTests {
    pwd: PathBuf
}

impl TestFixture for BuiltinTests {
    fn setup(&mut self) {
        let mut pwd = env::temp_dir();
        pwd.push("pwd");

        fs::create_dir(&pwd);
        self.pwd = pwd;
    }

    fn teardown(&mut self) {
        fs::remove_dir(&self.pwd);
    }

    fn tests(&mut self) -> Vec<Box<Fn(&mut BuiltinTests)>> {
        vec![Box::new(BuiltinTests::cd_with_no_args)]
    }
}

impl BuiltinTests {
    fn new() -> BuiltinTests {
        BuiltinTests {
            pwd: PathBuf::new()
        }
    }
}

fn cd_with_no_args(&mut self) {
    let home = String::from("/");
    env::set_var("HOME", &home);

    let mut cd = Cd::new();
    cd.run(&[]);

    assert_eq!(env::var("PWD"), Ok(home));
}

#[test]
fn cd_tests() {
    let mut builtin_tests = BuiltinTests::new();
    test_fixture_runner(&mut builtin_tests);
}

我使用线程的全部意图是与测试运行器隔离。 如果测试失败,则会引起恐慌,导致跑步者死亡。 感谢您的任何见解,我愿意改变我的设计,如果这将解决恐慌问题。

您的代码有几个问题,我将向您展示如何逐一修复它们。

第一个问题是你使用map()迭代迭代器。 它将无法正常工作,因为map()是惰性的 - 除非你使用迭代器,否则传递给它的闭包将不会运行。 正确的方法是使用for循环:

for t in fixture().tests().iter() {

其次,您通过引用迭代闭包向量:

fixture.tests().iter().map(|t| {

Vec<T>上的iter()返回一个迭代器,产生类型为&T ,因此你的t将是&Box<Fn(&mut Self)> 但是, Box<Fn(&mut T)>默认情况下不实现Sync (它是一个特征对象,除了你明确指定的之外没有关于底层类型的信息),所以&Box<Fn(&mut T)>不能用于多个线程。 这就是你看到的第二个错误。

很可能你不想通过引用使用这些闭包; 你可能想完全将它们移动到衍生线程。 为此,您需要使用into_iter()而不是iter()

for t in fixture.tests().into_iter() {

现在t将是Box<Fn(&mut T)> 但是,它仍然无法跨线程发送。 同样,它是一个特征对象,编译器不知道其中包含的类型是否为Send 为此,您需要将Send bound添加到闭包的类型:

fn tests(&mut self) -> Vec<Box<Fn(&mut Self)+Send>>

现在关于Fn的错误消失了。

最后一个错误是关于Send没有为T实现。 我们需要在T上添加一个Send bound:

pub fn test_fixture_runner<T: TestFixture+Send>(fixture: &mut T) {

现在,错误变得更容易理解:

test.rs:18:22: 18:35 error: captured variable `fixture` does not outlive the enclosing closure
test.rs:18         let handle = thread::spawn(move || {
                                ^~~~~~~~~~~~~
note: in expansion of closure expansion
test.rs:18:5: 28:6 note: expansion site
test.rs:15:66: 31:2 note: captured variable is valid for the anonymous lifetime #1 defined on the block at 15:65
test.rs:15 pub fn test_fixture_runner<T: TestFixture+Send>(fixture: &mut T) {
test.rs:16     fixture.setup();
test.rs:17
test.rs:18     for t in fixture.tests().into_iter() {
test.rs:19         let handle = thread::spawn(move || {
test.rs:20             fixture.before_each();
           ...
note: closure is valid for the static lifetime

发生此错误是因为您尝试在spawn() ed线程中使用引用。 spawn()要求其closure参数具有'static绑定”,也就是说,其捕获的环境不得包含非'static生命周期的引用。 但这正是这里发生的事情 - &mut T不是'static spawn()设计并不禁止避免连接,因此显式编写它以禁止向生成的线程传递非'static引用。

请注意,当你使用&mut T ,这个错误是不可避免的,即使你把&mut T放在Arc ,因为那时&mut T的生命周期将被“存储”在Arc ,所以Arc<Mutex<&mut T>>也赢了不是'static

有两种方法可以做你想要的。

首先,您可以使用不稳定的thread::scoped() API。 它是不稳定的,因为它表明安全代码中的内存不安全,并且计划将来为它提供某种替代。 但是,您可以在夜间使用Rust(它不会导致内存不安全,仅在特殊情况下):

pub fn test_fixture_runner<T: TestFixture+Send>(fixture: &mut T) {
    fixture.setup();

    let tests = fixture.lock().unwrap().tests();
    for t in tests.into_iter() {
        let f = &mut *fixture;

        let handle = thread::scoped(move || {
            f.before_each();
            t(f);
            f.after_each();
        });

        handle.join();
    }

    fixture.teardown();
}

此代码编译是因为scoped()以这样的方式编写,即它保证(在大多数情况下)线程不会超过所有捕获的引用。 我不得不重新使用fixture因为否则(因为&mut引用不可复制)它将被移入线程并且fixture.teardown()将被禁止。 此外,我不得不提取tests变量,因为否则互斥锁将在for循环的持续时间内被主线程锁定,这自然会禁止在子线程中锁定它。

但是,使用scoped()您无法隔离子线程中的混乱。 如果子线程发生混乱,则会从join()调用中重新抛出此恐慌。 这通常可能是一个问题,也可能不是,但我认为这对您的代码来说一个问题。

另一种方法是重构代码,从一开始就在Arc<Mutex<..>>保存夹具:

pub fn test_fixture_runner<T: TestFixture + Send + 'static>(fixture: Arc<Mutex<T>>) {
    fixture.lock().unwrap().setup();

    for t in fixture.lock().unwrap().tests().into_iter() {
        let fixture = fixture.clone();

        let handle = thread::spawn(move || {
            let mut fixture = fixture.lock().unwrap();

            fixture.before_each();
            t(&mut *fixture);
            fixture.after_each();
        });

        if let Err(_) = handle.join() {
            println!("Test failed!")
        } 
    }

    fixture.lock().unwrap().teardown();
}

请注意,现在T也必须是'static ,因为否则它不能与thread::spawn()一起使用,因为它需要'static 内部闭包内的fixture不是&mut T而是MutexGuard<T> ,因此必须将其显式转换为&mut T才能将其传递给t

这可能看起来过于庞大且不必要地复杂,但是,这种编程语言的设计确实可以防止您在多线程编程中产生许多错误。 我们看到的上述每一个错误都是有效的 - 如果被忽略,它们中的每一个都会成为记忆不安全或数据竞争的潜在原因。

Rust HandBook的Concurrency部分所述

当类型T实现Send时,它向编译器指示此类型的某些东西能够在线程之间安全地传输所有权。

如果未实现“发送”,则无法在线程之间传输所有权。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM