简体   繁体   English

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

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

I am attempting to write a simpler unit test runner for my Rust project. 我正在尝试为Rust项目编写一个更简单的单元测试运行器。 I have created a TestFixture trait that my test fixture structs will implement, similar to inheriting from the unit test base class in other testing frameworks. 我创建了一个TestFixture特性,我的测试夹具结构将实现,类似于继承其他测试框架中的单元测试基类。 The trait is fairly simple. 特点很简单。 This is my test fixture 这是我的测试夹具

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()
    }
}

My test running function is as follows 我的测试运行功能如下

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();
}

I get the error 我收到了错误

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

I have tried adding Arcs around the types being sent to the thread, no dice, same error. 我尝试在发送到线程的类型周围添加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();
}

A sample test fixture would be something like 样品测试夹具就像是

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);
}

My whole intention of using threads is isolation from the test runner. 我使用线程的全部意图是与测试运行器隔离。 If a test fails an assertion it causes a panic which kills the runner. 如果测试失败,则会引起恐慌,导致跑步者死亡。 Thanks for any insight, I'm willing to change my design if that will fix the panic problem. 感谢您的任何见解,我愿意改变我的设计,如果这将解决恐慌问题。

There are several problems with your code, I'll show you how to fix them one by one. 您的代码有几个问题,我将向您展示如何逐一修复它们。

The first problem is that you're using map() to iterate over an iterator. 第一个问题是你使用map()迭代迭代器。 It won't work correctly because map() is lazy - unless you consume the iterator, the closure you passed to it won't run. 它将无法正常工作,因为map()是惰性的 - 除非你使用迭代器,否则传递给它的闭包将不会运行。 The correct way is to use for loop: 正确的方法是使用for循环:

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

Second, you're iterating the vector of closures by reference: 其次,您通过引用迭代闭包向量:

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

iter() on a Vec<T> returns an iterator yielding items of type &T , so your t will be of type &Box<Fn(&mut Self)> . Vec<T>上的iter()返回一个迭代器,产生类型为&T ,因此你的t将是&Box<Fn(&mut Self)> However, Box<Fn(&mut T)> does not implement Sync by default (it is a trait object which have no information about the underlying type except that you specified explicitly), so &Box<Fn(&mut T)> can't be used across multiple threads. 但是, Box<Fn(&mut T)>默认情况下不实现Sync (它是一个特征对象,除了你明确指定的之外没有关于底层类型的信息),所以&Box<Fn(&mut T)>不能用于多个线程。 That's what the second error you see is about. 这就是你看到的第二个错误。

Most likely you don't want to use these closures by reference; 很可能你不想通过引用使用这些闭包; you probably want to move them to the spawned thread entirely. 你可能想完全将它们移动到衍生线程。 For this you need to use into_iter() instead of iter() : 为此,您需要使用into_iter()而不是iter()

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

Now t will be of type Box<Fn(&mut T)> . 现在t将是Box<Fn(&mut T)> However, it still can't be sent across threads. 但是,它仍然无法跨线程发送。 Again, it is a trait object, and the compiler does not know if the type contained inside is Send . 同样,它是一个特征对象,编译器不知道其中包含的类型是否为Send For this you need to add Send bound to the type of the closure: 为此,您需要将Send bound添加到闭包的类型:

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

Now the error about Fn is gone. 现在关于Fn的错误消失了。

The last error is about Send not being implemented for T . 最后一个错误是关于Send没有为T实现。 We need to add a Send bound on T : 我们需要在T上添加一个Send bound:

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

And now the error becomes more comprehensible: 现在,错误变得更容易理解:

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

This error happens because you're trying to use a reference in a spawn() ed thread. 发生此错误是因为您尝试在spawn() ed线程中使用引用。 spawn() requires its closure argument to have 'static bound, that is, its captured environment must not contain references with non- 'static lifetimes. spawn()要求其closure参数具有'static绑定”,也就是说,其捕获的环境不得包含非'static生命周期的引用。 But that's exactly what happens here - &mut T is not 'static . 但这正是这里发生的事情 - &mut T不是'static spawn() design does not prohibit avoiding joining, so it is explicitly written to disallow passing non- 'static references to the spawned thread. spawn()设计并不禁止避免连接,因此显式编写它以禁止向生成的线程传递非'static引用。

Note that while you're using &mut T , this error is unavoidable, even if you put &mut T in Arc , because then the lifetime of &mut T would be "stored" in Arc and so Arc<Mutex<&mut T>> also won't be 'static . 请注意,当你使用&mut T ,这个错误是不可避免的,即使你把&mut T放在Arc ,因为那时&mut T的生命周期将被“存储”在Arc ,所以Arc<Mutex<&mut T>>也赢了不是'static

There are two ways to do what you want. 有两种方法可以做你想要的。

First, you can use the unstable thread::scoped() API. 首先,您可以使用不稳定的thread::scoped() API。 It is unstable because it is shown to allow memory unsafety in safe code, and the plan is to provide some kind of replacement for it in the future. 它是不稳定的,因为它表明安全代码中的内存不安全,并且计划将来为它提供某种替代。 However, you can use it in nightly Rust (it won't cause memory unsafety by itself, only in specifically crafted situations): 但是,您可以在夜间使用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();
}

This code compiles because scoped() is written in such a way that it guarantees (in most cases) that the thread won't outlive all captured references. 此代码编译是因为scoped()以这样的方式编写,即它保证(在大多数情况下)线程不会超过所有捕获的引用。 I had to reborrow fixture because otherwise (because &mut references aren't copyable) it would be moved into the thread and fixture.teardown() would be prohibited. 我不得不重新使用fixture因为否则(因为&mut引用不可复制)它将被移入线程并且fixture.teardown()将被禁止。 Also I had to extract tests variable because otherwise the mutex will be locked by the main thread for the duration of the for loop which would naturally disallow locking it in the child threads. 此外,我不得不提取tests变量,因为否则互斥锁将在for循环的持续时间内被主线程锁定,这自然会禁止在子线程中锁定它。

However, with scoped() you can't isolate the panic in the child thread. 但是,使用scoped()您无法隔离子线程中的混乱。 If the child thread panics, this panic will be rethrown from join() call. 如果子线程发生混乱,则会从join()调用中重新抛出此恐慌。 This may or may not be a problem in general, but I think it is a problem for your code. 这通常可能是一个问题,也可能不是,但我认为这对您的代码来说一个问题。

Another way is to refactor your code to hold the fixture in Arc<Mutex<..>> from the beginning: 另一种方法是重构代码,从一开始就在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();
}

Note that now T has also to be 'static , again, because otherwise it couldn't be used with thread::spawn() as it requires 'static . 请注意,现在T也必须是'static ,因为否则它不能与thread::spawn()一起使用,因为它需要'static fixture inside the inner closure is not &mut T but a MutexGuard<T> , and so it has to be explicitly converted to &mut T in order to pass it to t . 内部闭包内的fixture不是&mut T而是MutexGuard<T> ,因此必须将其显式转换为&mut T才能将其传递给t

This may seem overly and unnecessarily complex, however, such design of a programming language does prevent you from making many errors in multithreaded programming. 这可能看起来过于庞大且不必要地复杂,但是,这种编程语言的设计确实可以防止您在多线程编程中产生许多错误。 Each of the above errors we have seen is valid - each of them would be a potential cause of memory unsafety or data races if it was ignored. 我们看到的上述每一个错误都是有效的 - 如果被忽略,它们中的每一个都会成为记忆不安全或数据竞争的潜在原因。

As stated in the Rust HandBook's Concurrency section : Rust HandBook的Concurrency部分所述

When a type T implements Send, it indicates to the compiler that something of this type is able to have ownership transferred safely between threads. 当类型T实现Send时,它向编译器指示此类型的某些东西能够在线程之间安全地传输所有权。

If you do not implement Send, ownership cannot be transfered between threads. 如果未实现“发送”,则无法在线程之间传输所有权。

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

相关问题 C#中的多线程:如何将函数名称传递给另一个函数来启动新线程? - Multithreading in C#: How can I pass a function name to another function to start a new thread? 如何更新另一个线程上的控件? - How can I update a control on another thread? 如何在另一个线程中暂停/中断boost :: thread? - How can I pause/interrupt boost::thread within another thread? 在Java中:如何让线程监视另一个线程? - In Java: how can I make thread watch over another thread? 如何从另一个线程访问一个线程的方法? - How can I access the method of one thread from another thread? 如何删除单独的工作线程 function 并创建一个线程 - How can I remove seperate worker thread function and create a thread 如何在 Python 中以非阻塞线程安全的方式将变量/标志发送到另一个线程? - How do I send a variable/flag to another thread in a non-blocking thread safe way in Python? 如何从特定线程发送udp数据报? - How can I send udp datagrams from a specific thread? 如何使用后台工作程序将参数(字符串)发送到UI线程? - How can I send arguments(string) to the UI thread with the Background Worker? 如何将通用 T 发送到另一个线程? - How to send generic T to another thread?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM