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

时间:2015-06-19 05:29:23

标签: multithreading unit-testing rust

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

int main(void)
{
   FILE *fp;
   int c;

   fp = fopen("myfile.txt","r");
   if(fp == NULL) 
   {
      perror("Error in opening file");
      return(-1);
   } do {
      c = fgetc(fp);
      if(feof(fp))
         break ;
    if (c != '\n')
        printf("%c", c);
   }while(1);

   fclose(fp);
}

我的测试运行功能如下

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

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

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

样本测试夹具将类似于

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

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

2 个答案:

答案 0 :(得分:7)

您的代码存在一些问题,我将向您展示如何逐一解决这些问题。

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

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

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

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

Vec<T>会返回一个迭代器,产生&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绑定到闭包类型:

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

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

最后一个错误是Send没有实现T。我们需要在Send上添加T界限:

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不是'staticspawn()设计并不禁止避免加入,因此明确写入以禁止对生成的线程传递非'static引用。

请注意,在您使用&mut T时,即使您将&mut T放入Arc,此错误也是不可避免的,因为&mut T的生命周期将会be&#34;存储&#34;在ArcArc<Mutex<&mut T>> 'static也不会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(); }

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

首先,您可以使用不稳定的thread::scoped() API。它是不稳定的,因为it is shown允许安全代码中的内存不安全,并且计划将来为它提供某种替代。但是,您可以在夜间使用Rust(它本身不会造成记忆不安全,只能在特殊情况下使用):

scoped()

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

然而,使用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,因此必须将其显式转换为SELECT PO.Item,PO.PONO, PO.Qty as po_qty, GRN.Qty as GRN_qty, PO.Qty- GRN.Qty as GRN_balance from PO,GRN group by PO.Item 才能将其传递给{{1}}。

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

答案 1 :(得分:0)

Rust HandBook's Concurrency section中所述:

  

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

如果您没有实施发送,则无法在线程之间转移所有权。