Futures for CPU-bound tasks

Futures, as I understand them, are meant to make progress (decently) quickly, and then return control to the executing thread pool with a Ready (result) or a NotReady (waiting on some resource). This allows the executor to then move on to other futures which have been woken up because the resource they're waiting on has arrived.

This works great for an application where you're waiting on user input to prompt mainly IO-bound results, where most of the wall-clock time is waiting on hardware/network/other and not on CPU cycles.

But what happens when you start putting CPU bound tasks in futures?

Tokio (though I'd prefer a more general answer than just with the tokio library) does say that a future can be

A long-running CPU-intensive task , running on a thread pool; when the task finishes, the future is completed, and its value is the return value of the task.

But what if your (tokio) executor is already a thread pool itself?

I'd like to effectively "dispatch" CPU-intensive work to the same thread pool. I'm fine staying with cooperative multitasking and adding a yield or await!() or similar on back-edges or between chunks of work. But I think (though I could accept being told otherwise) that staying with a singular work-stealing executor for work would be better than having one IO-bound executor and one CPU-bound work-stealing executor, each with a tread count equivalent to my (logical) core count.

1 Like

Disclaimer: I haven't personally tried this, but there should be nothing preventing you from spawning additional threads even when you're already using a thread pool for something.

I think you'd have to implement some wrapper type that implements Future. The only required method is poll, which would have to check whether the thread is complete. You could then have your wrapper type spawn a thread that runs some CPU intensive function, letting the OS take care of all scheduling.

If you're using a thread pool-backed tokio runtime, you can use a special function to switch over to "blocking mode", which ensures that there's still capacity to run normal non-CPU bound tasks while your work completes: tokio_threadpool::blocking - Rust.

6 Likes

Create a futures_cpupool::CpuPool, and run the heavy code inside cpu_pool.spawn_fn(|| …). Spawn returns a Future which can be combined with other I/O-based futures.

3 Likes