pub fn park()
Expand description
阻塞,除非或直到当前线程的 token 可用为止。
对 park
的调用不能保证线程将永远保持驻留状态,因此调用者应为此做好准备。
但是,保证这个函数不会 panic (如果执行遇到一些罕见的错误,它可能会中止进程)。
park 和 unpark
每个线程都通过 thread::park
函数和 thread::Thread::unpark
方法提供了一些基本的阻塞支持。
park
阻塞当前线程,然后可以通过在阻塞线程的句柄上调用 unpark
方法从另一个线程恢复当前线程。
从概念上讲,每个 Thread
句柄都有一个关联的 token,该 token 最初不存在:
-
thread::park
函数会阻塞当前线程,除非或直到 token 可用于其线程句柄为止,否则该原子将自动消耗 token。 它也可能虚假地返回,而不消耗 token。thread::park_timeout
执行相同的操作,但允许指定阻塞线程的最长时间。 -
Thread
上的unpark
方法原子地使 token (如果尚未提供) 可用。 由于最初不存在 token,因此unpark
后跟park
将导致第二个调用立即返回。
换句话说,每个 Thread
的行为都类似于自旋锁,可以使用 park
和 unpark
进行锁定和解锁。
请注意,被取消阻止并不意味着与取消该线程的某个人进行任何同步,这也可能是虚假的。
例如,将 park
和 unpark
都立即返回而无需执行任何操作将是有效但效率低下的实现。
通常通过获取当前线程的句柄,将该句柄放置在共享数据结构体中,以便其他线程可以找到它,然后 park
在循环中来使用该 API。
当满足某些所需条件时,另一个线程将在句柄上调用 unpark
。
这种设计的动机是双重的:
-
在构建新的同步原语时,它无需分配互斥锁和 condvar。线程已经提供了基本的 blocking/signaling。
-
它可以在许多平台上非常有效地实现。
Examples
use std::thread;
use std::sync::{Arc, atomic::{Ordering, AtomicBool}};
use std::time::Duration;
let flag = Arc::new(AtomicBool::new(false));
let flag2 = Arc::clone(&flag);
let parked_thread = thread::spawn(move || {
// 我们要等到标志被设置。
// 我们可以旋转,但是使用 park/unpark 效率更高。
while !flag2.load(Ordering::Acquire) {
println!("Parking thread");
thread::park();
// 我们可以伪装地到达这里,即在下面的 10ms 结束之前!
// 但这没问题,我们一直处于循环状态,直到仍然设置了标志。
println!("Thread unparked");
}
println!("Flag received");
});
// 花费一些时间来生成线程。
thread::sleep(Duration::from_millis(10));
// 设置标志,并让线程唤醒。
// 这里没有竞态条件,如果 `unpark` 首先出现,则 `park` 将立即返回。
// 因此,没有死锁的风险。
flag.store(true, Ordering::Release);
println!("Unpark the thread");
parked_thread.thread().unpark();
parked_thread.join().unwrap();
Run