为何Rust作用域线程(scoped threads)会忽略指定的recv_timeout时长?
作用域线程超时未生效的原因与解决方法
问题描述
尝试为作用域线程设置超时,复现代码如下:
use std::thread; use std::sync::mpsc; use std::fmt::Display; use std::time::Duration; trait Foo<T: Display + Clone> { fn fun(&self) -> T; } struct Bar<T: Display + Clone> { pub x: T, } impl<T: Display + Clone> Foo<T> for Bar<T> { fn fun(&self) -> T { thread::sleep(Duration::from_secs(100)); self.x.clone() } } fn test(boxed: &Box<dyn Foo<usize> + Sync>) -> Option<usize> { let (sender, receiver) = mpsc::channel(); thread::scope(|s| { s.spawn(move || { match sender.send(boxed.fun()) { Ok(_) => {}, Err(_) => {}, } }); }); let res = match receiver.recv_timeout(Duration::from_secs(2)) { Ok(n) => Some(n), Err(_) => None, }; return res; } pub fn main() { let boxed: Box<dyn Foo<usize> + Sync> = Box::new(Bar{x: 10}); println!("{:?}", test(&boxed)); }
预期线程休眠100秒,但程序阻塞2秒后触发超时;实际线程会运行满100秒,超时从未生效。此前未使用作用域线程时功能正常,推测与作用域线程有关,请问为何作用域线程不会在2秒后超时?
原因分析
问题出在thread::scope的核心特性上:thread::scope会阻塞当前线程,直到所有在该作用域内创建的子线程完全执行完毕。
在原代码中,thread::scope(...)这一行会一直等待那个休眠100秒的子线程执行结束,之后才会继续执行后面的recv_timeout逻辑。此时子线程已经完成了send操作,recv_timeout自然不会触发超时。
而使用非作用域线程(thread::spawn)时,主线程不会等待子线程完成,会直接执行后续的recv_timeout,所以超时逻辑能正常工作。
解决方法
要让超时逻辑生效,需要将recv_timeout的逻辑移到thread::scope内部,这样就能在等待子线程的同时触发超时判断,而不是等子线程完全结束后才执行超时逻辑。修改后的代码如下:
use std::thread; use std::sync::mpsc; use std::fmt::Display; use std::time::Duration; trait Foo<T: Display + Clone> { fn fun(&self) -> T; } struct Bar<T: Display + Clone> { pub x: T, } impl<T: Display + Clone> Foo<T> for Bar<T> { fn fun(&self) -> T { thread::sleep(Duration::from_secs(100)); self.x.clone() } } fn test(boxed: &Box<dyn Foo<usize> + Sync>) -> Option<usize> { let (sender, receiver) = mpsc::channel(); thread::scope(|s| { // 启动作用域子线程 s.spawn(move || { let _ = sender.send(boxed.fun()); }); // 在作用域内执行超时接收 match receiver.recv_timeout(Duration::from_secs(2)) { Ok(n) => Some(n), Err(_) => None, } }) } pub fn main() { let boxed: Box<dyn Foo<usize> + Sync> = Box::new(Bar{x: 10}); println!("{:?}", test(&boxed)); }
修改后,test函数会在2秒内返回None,而作用域内的子线程会继续运行到100秒结束,但不会阻塞主线程的流程。
内容的提问来源于stack exchange,提问作者terrabyte
相关产品推荐
相关产品推荐

