为什么在返回`NotReady`后没有重复调用`Future :: poll`?

时间:2017-04-19 18:57:00

标签: multithreading rust future

考虑以下代码

extern crate futures;

use std::sync::{atomic, Arc};
use futures::*;

struct F(Arc<atomic::AtomicBool>);

impl Future for F {
    type Item = ();
    type Error = ();

    fn poll(&mut self) -> Result<Async<Self::Item>, Self::Error> {
        println!("Check if flag is set");
        if self.0.load(atomic::Ordering::Relaxed) {
            Ok(Async::Ready(()))
        } else {
            Ok(Async::NotReady)
        }
    }
}

fn main() {
    let flag = Arc::new(atomic::AtomicBool::new(false));
    let future = F(flag.clone());
    ::std::thread::spawn(move || {
        ::std::thread::sleep_ms(10);
        println!("set flag");
        flag.store(true, atomic::Ordering::Relaxed);
    });
    // ::std::thread::sleep_ms(20);
    let result = future.wait();
    println!("result: {:?}", result);
}

生成的线程设置了一个未来等待的标志。 我们还会睡眠生成的线程,因此来自.poll()的初始.wait()调用是在设置标志之前。这会导致.wait()无限制地阻塞(看似)。如果我们取消注释其他thread::sleep_ms.wait()会返回,并打印出结果(())。

我希望当前线程多次调用poll来尝试解决未来问题,因为我们正在阻止当前线程。但是,这种情况并没有发生。

我试过阅读some docs,问题似乎是第一次从park获取NotReady后线程poll。但是,我不清楚为什么这是什么,或者如何解决这个问题。

我错过了什么?

1 个答案:

答案 0 :(得分:7)

为什么你需要停下等待的未来而不是反复轮询?答案很明显,恕我直言。因为在一天结束时它会更快更有效!

要重复轮询未来(可能被称为“忙碌等待”),图书馆必须决定是经常做还是很少做,而且回答都不令人满意。经常做,你浪费CPU周期,很少做,代码反应迟钝。

所以是的,你需要在等待某事时停放任务,然后在等待时取消停放。像这样:

#![allow(deprecated)]

extern crate futures;

use std::sync::{Arc, Mutex};
use futures::*;
use futures::task::{park, Task};

struct Status {
    ready: bool,
    task: Option<Task>,
}

#[allow(dead_code)]
struct F(Arc<Mutex<Status>>);

impl Future for F {
    type Item = ();
    type Error = ();

    fn poll(&mut self) -> Result<Async<Self::Item>, Self::Error> {
        println!("Check if flag is set");
        let mut status = self.0.lock().expect("!lock");
        if status.ready {
            Ok(Async::Ready(()))
        } else {
            status.task = Some(park());
            Ok(Async::NotReady)
        }
    }
}

#[test]
fn test() {
    let flag = Arc::new(Mutex::new(Status {
                                       ready: false,
                                       task: None,
                                   }));
    let future = F(flag.clone());
    ::std::thread::spawn(move || {
        ::std::thread::sleep_ms(10);
        println!("set flag");
        let mut status = flag.lock().expect("!lock");
        status.ready = true;
        if let Some(ref task) = status.task {
            task.unpark()
        }
    });
    let result = future.wait();
    println!("result: {:?}", result);
}

请注意Future::poll在这里做了几件事:它正在检查一个外部条件,它正在停止任务,所以可以进行比赛,比如:

  1. poll检查变量并发现它是false;
  2. 外部代码将变量设置为true;
  3. 外部代码检查任务是否停放并发现它不是;
  4. poll执行任务,但繁荣!现在为时已晚,没有人会再停车了。
  5. 为了避免任何比赛,我使用Mutex来同步这些互动。

    P.S。如果只需要将线程结果包装到Future中,那么请考虑使用oneshot通道:它已经Receiver实现了Future接口。