How to run multiple futures that call thread::sleep in parallel? [duplicate]
Asked Answered
N

1

7

I have a slow future that blocks for 1 second before running to completion.

I've tried to use the join combinator but the composite future my_app executes the futures sequentially:

#![feature(pin, futures_api, arbitrary_self_types)]

extern crate futures; // v0.3

use futures::prelude::*;
use futures::task::Context;
use std::pin::PinMut;
use std::{thread, time};
use futures::executor::ThreadPoolBuilder;

struct SlowComputation {}

impl Future for SlowComputation {
    type Output = ();

    fn poll(self: PinMut<Self>, _cx: &mut Context) -> Poll<Self::Output> {
        let millis = time::Duration::from_millis(1000);
        thread::sleep(millis);

        Poll::Ready(())
    }
}

fn main() {
    let fut1 = SlowComputation {};
    let fut2 = SlowComputation {};
    let my_app = fut1.join(fut2);

    ThreadPoolBuilder::new()
        .pool_size(5)
        .create()
        .expect("Failed to create threadpool")
        .run(my_app);
}

Why does join work like that? I expected the futures to be spawned on different threads.

What is the right way to obtain my goal?

Cargo.toml:

[dependencies]
futures-preview = "0.3.0-alfa.6"

Result:

$ time target/debug/futures03

real    0m2.004s
user    0m0.000s
sys 0m0.004s
Njord answered 13/9, 2018 at 11:44 Comment(1)
I think the way you want to go is rayonTact
G
7

If you combine futures with join() they'll be transformed into a single task, running on a single thread.

If the futures are well-behaved, they would run in parallel in an event-driven (asynchronous) manner. You would expect your application to sleep for 1 second.

But unfortunately the future you implemented is not well-behaved. It blocks the current thread for one second, disallowing any other work to be done during this time. Because the futures are run on the same thread, they cannot run at the same time. Your application will sleep for 2 seconds.

Note that if you change your example to the following, the futures will remain separate tasks and you can run them independently in parallel on your thread pool:

fn main() {
    let fut1 = SlowComputation {};
    let fut2 = SlowComputation {};

    let mut pool = ThreadPoolBuilder::new()
        .pool_size(5)
        .create()
        .expect("Failed to create threadpool");

    pool.spawn(fut1);
    pool.run(fut2);
}

Writing futures that block the main thread is highly discouraged and in a real application you should probably use timers provided by a library, for example tokio::timer::Delay or tokio::timer::timeout::Timeout.

Gaskill answered 13/9, 2018 at 12:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.