簡體   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