thread
Rust中的执行的程序由操作系统原生线程组成,每个线程由自己的栈和本地状态。线程可以命名、提供一些低级的同步支持。线程之间的通信可以通过channels完成。rust的传入的类型、其它形式的线程同步、共享内存数据结构。线程之间的通信可以通过通道(Rust 的消息传递类型)以及其他形式的线程同步和共享内存数据结构来完成 。特别是,使用原子引用计数容器 可以在线程之间轻松共享保证线程安全的类型Arc。Rust 中的致命逻辑错误会导致线程异常,在此期间线程将展开堆栈,运行析构函数并释放拥有的资源。虽然并不意味着“try/catch”机制,但 Rust 中的panic仍然可以用捕获(除非用 编译panic=abort)并 catch_unwind从中恢复,或者用恢复 resume_unwind。如果没有捕获panic,线程将退出,但可以选择从不同的线程检测 panic join。如果主线程发生panic而没有捕获panic,则应用程序将以非零退出代码退出。当 Rust 程序的主线程终止时,整个程序都会关闭,即使其他线程仍在运行。然而,该模块提供了方便的设施来自动等待线程的终止(即,加入)。
创建一个线程
use std::thread;
use std::time;
fn main() {
let t = std::thread::current();
let name = t.name().unwrap_or("Default");
println!("Currrent Thread = {}", name);
thread::spawn(|| {
let t = std::thread::current();
let name = t.name().unwrap_or("sub-thread");
println!("sub thread = {name}");
});
std::thread::sleep(time::Duration::from_secs(1));//不加这一行,子线程将无输出
}
在上面的例子中,生成的线程是detach的。这意味着我们无法知道这个线程的运行状态。为了知道线程何时运行完成,需要调用spwan返回JoinHandle对象,这个对象提供join方法允许调用者等待生成的线程结果返回。
use std::thread;
fn main() {
let t = std::thread::current();
let name = t.name().unwrap_or("Default");
println!("Currrent Thread = {}", name);
let sub_thread = thread::spawn(|| {
let t = std::thread::current();
let name = t.name().unwrap_or("sub-thread");
println!("sub thread = {name}");
});
let _ = sub_thread.join();
}
上面的代码中返回join()返回的是thread::Result
线程成功生成这Result包含的是Ok否则人thread 报错则为Err。
配置线程
线程可以通过Builder配置线程名称和线程的栈大小:
线程类型
- 生成一个新的线程使用thread::spawn函数,在Joinhandle上调用thread。
- 使用thread::current请求当前线程。
thread::current 在handle不是spawn接口生成的依旧可以用。
线程的本地存储
该模块还为 Rust 程序提供了线程本地存储的实现。线程本地存储是一种将数据存储到全局变量中的方法,程序中的每个线程都有自己的副本。线程之间不共享此数据,因此不需要同步访问。
线程本地键拥有它所包含的值,并将在线程退出时销毁该值。它是用thread_local!宏创建的,可以包含任何值’static(无borrow指针)。它提供了一个访问函数 , 用该函数生成对指定闭包的值的共享引用。线程本地键仅允许对值进行共享访问,因为如果允许mut borrow借用,则无法保证唯一性。大多数值都希望通过CellRef、Cell类型利用interior mutability。
命名线程
线程能够具有用于识别目的的关联名称。默认情况下,生成的线程是未命名的。要指定线程的名称,请使用Builder构建线程并将Builder所需的线程名称传递给Builder::name。要从线程内检索线程名称,请使用Thread::name。使用线程名称的几个示例:
- 如果指定线程中发生panic,则线程名称将打印在panic消息中。
- 线程名称被提供给适用的操作系统(例如,pthread_setname_np在类 UNIX 平台中)。
堆栈大小
默认堆栈大小取决于平台并且可能会发生变化。目前,所有 Tier-1 平台上的大小均为 2 MiB。有两种方法可以手动指定生成线程的堆栈大小:
- 使用 构建线程Builder并将所需的堆栈大小传递给Builder::stack_size。
- 将环境变量设置RUST_MIN_STACK为表示所需堆栈大小(以字节为单位)的整数。请注意,设置Builder::stack_size将覆盖此设置。RUST_MIN_STACK请注意,程序启动后可能会忽略对 的更改。
请注意,主线程的堆栈大小不是由Rust 决定的。
结构体
- AccessError:由返回的错误LocalKey::try_with。
- Builder:线程工厂,可用于配置新线程的属性。
- JoinHandle:加入线程的拥有权限(在其终止时阻止)。
- LocalKey:拥有其内容的线程本地存储键。
- Scope:在其中生成作用域线程的作用域。
- ScopeJoinHanfle:拥有加入作用域线程的权限(在其终止时阻止)。
- Thread:线程的句柄。
- threadId:正在运行的线程的唯一标识符。
函数
- available_parallelism:返回程序应使用的默认并行度的估计值。
- current:获取调用它的线程的句柄。
- panicking:确定当前线程是否因panic而展开。
- park:阻塞,除非或直到当前线程的令牌可用。
- pack_timeout:阻塞,除非或直到当前线程的令牌可用或已达到指定的持续时间(可能会虚假唤醒)。
- scope:创建一个用于生成作用域线程的作用域。
- sleep:让当前线程休眠至少指定的时间。
- spwan:生成一个新的线程,返回一个JoinHandle的对象。
- yield_now:协同地将时间片交给操作系统调度程序。
标准线程库结构体
Builder
线程工厂,可用于配置新线程的属性。可以在其上方法以对其进行配置。可用的两种配置是:
name
:指定线程的关联名称。stack_size
:指定线程所需的堆栈大小。
该spawn方法将取得Builder的所有权,并返回io::Result给定的配置创建线程句柄。free函数thread::spawn使用Builder默认配置的 a 及其unwrap返回值。
当您想要从启动线程失败中恢复时,您可能想使用spawn而不是使用thread::spawn,实际上 free 函数会出现恐慌,该方法Builder将返回io::Result.
use std::thread;
let builder = thread::Builder::new();
let handler = builder.spawn(|| {
// thread code
}).unwrap();
handler.join().unwrap();
实现方法
fn spawn_scoped<'scope, 'env, F, T>(self,scope: &'scope Scope<'scope, 'env>,f: F) -> Result<ScopedJoinHandle<'scope, T>> where F: FnOnce() -> T + Send + 'scope,T: Send + 'scope
:通过设置Builder生成一个新的scope域的线程。new() -> Builder
:生成基础配置下的spawn线程,可以通过链式配置。name(self, name: String) -> Builder
:设置线程的名称,名称不能包含\0
。stack_size(self, size: usize) -> Builder
:设置创建新线程的栈大小。实际的栈大小必须大于这个值。spawn<F, T>(self, f: F) -> Result<JoinHandle<T>> where F: FnOnce() -> T + Send + 'static,T: Send + 'static
:生成一个新的拥有Builder所有权的新线程。新生成的线程可能比调用者存活时间长(除非调用者是主线程,进程对出后终止所有线程)。spawn_unchecked<'a, F, T>(self, f: F) -> Result<JoinHandle<T>> where F: FnOnce() -> T + Send + 'a,T: Send + 'a
(unsafe):通过获取 的所有权来生成一个没有任何生命周期限制的新线程Builder,返回io::JoinHandle
加入到Result。生成的线程可能比调用者线程存活得更久(除非调用者线程是主线程;当主线程完成时,整个进程就会终止)。JoinHandle可用于阻止生成的线程终止,包括恢复其panic。此方法与 相同thread::Builder::spawn
,但放宽的生命周期界限使其不安全。有关更完整的文档,请参阅thread::spawn
。- 错误
- 与 free 函数不同spawn,此方法生成一个 io::Result来捕获在操作系统级别创建线程的任何失败。
- panic
- 如果设置了线程名称并且它包含空字节,则会发生panic。
- 安全
- 调用者必须确保生成的线程不会比提供的线程闭包及其返回类型中的任何引用存活得更久。这可以通过两种方式来保证:
- 错误
- 确保join在删除任何引用的数据之前调用
- 仅使用具有’static生命周期界限的类型,即那些没有或只有 引用的’static类型(两者thread::Builder::spawn 都thread::spawn静态强制执行此属性)
future trait
pub trait Future {
type Output;
// Required method
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
future 表示通过使用async获得的异步计算。future是一个可能尚未完成计算的值。这种“异步值”使线程可以等待值可用的同时继续执行有用的工作。
poll 方法
方法poll
是future 的核心方法 ,poll试图将future 解析为最终值。如果值未准备好,则此方法不会阻塞。相反,当前任务计划在可以通过poll再次取得进一步进展时被唤醒。传递context给该poll 方法的 可以提供一个Waker,它是用于唤醒当前任务的句柄。使用 future 时,通常不会poll直接调用,而是调用 .await值。
所需的关联类型
来源
类型输出:完成输出是产生的价值类型。
所需方法
fn poll (self: Pin < &mut Self >, cx: &mut Context <'_>) -> Poll <Self:: Output >
:尝试将future解析为最终值,如果该值尚不可用,则注册当前任务以进行唤醒。返回值
该函数返回:
Poll::Pending
如果未来还没有准备好Poll::Ready(val)
val如果成功完成的话,未来的结果。
一旦future结束,客户就不应该再次调用poll。当 future 尚未准备好时,poll返回并存储从当前复制Poll::Pending
的克隆。一旦未来取得进展,这一点就会被唤醒。例如,等待套接字变得可读的 future 将调用并存储它。当信号到达其他地方表明套接字可读时, 将调用该套接字并唤醒套接字future的任务。一旦任务被唤醒,它应该再次尝试future,这可能会也可能不会产生最终值。WakerContextWaker.clone()WakerWaker::wakepoll
请注意,在多次调用 时poll,只有Waker从 Context传递到最近一次的调用才应安排接收唤醒。
运行时特征
future 本身是惰性的;他们必须积极地 poll才能取得进展,这意味着每次当前任务被唤醒时,它都应该积极地重新re-poll它仍然感兴趣的future。该poll函数不会在紧密循环中重复调用 - 相反,只有当 future 指示它已准备好取得进展时(通过调用wake())才应该调用它。如果您熟悉 Unix 上的poll(2)或select(2)系统调用,那么值得注意的是,future 通常不会遇到“所有唤醒必须轮询所有事件”的相同问题;他们更像epoll(4)。
的实现poll应该力求快速返回,并且不应该阻塞。快速返回可以防止不必要地阻塞线程或事件循环。如果提前知道调用 poll可能会花费一段时间,则应将工作卸载到线程池(或类似的东西)以确保可以poll快速返回。
panic
一旦 future 完成(Ready从返回poll), poll再次调用它的方法可能会出现panic、永远阻塞或导致其他类型的问题;该Future特征对此类调用的效果没有任何要求。然而,由于poll方法没有被标记unsafe,Rust 的通常规则适用:调用绝不能导致未定义的行为(内存损坏、函数的不正确使用unsafe等),无论未来的状态如何。