Function std::thread::park

1.0.0 · source ·
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 的行为都类似于自旋锁,可以使用 parkunpark 进行锁定和解锁。

请注意,被取消阻止并不意味着与取消该线程的某个人进行任何同步,这也可能是虚假的。 例如,将 parkunpark 都立即返回而无需执行任何操作将是有效但效率低下的实现。

通常通过获取当前线程的句柄,将该句柄放置在共享数据结构体中,以便其他线程可以找到它,然后 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