Open NPN opened 6 years ago
Thanks for the feedback. This probably relates to #15, and the documentation should be updated to answer these questions. I'll provide some thoughts inline so that anyone (maybe @NPN?) can try updating the outline to include these answers.
This makes sense, but because the Tokio Runtime uses a thread pool, does this really apply?
The short answer is: Yes. The threaded runtime is designed optimized for non-blocking tasks. Each thread has a work queue of tasks that only that thread can handle (kind of). If the thread blocks, the work queue cannot be processed.
Long running futures should be offloaded to a pool that can handle blocking tasks. The "Tokio way" of doing this is still a work in progress (see #15) so there isn't a short answer now.
Anyway, thanks for the report. We should definitely answer these questions in the doc.
What counts as blocking here? Say I have 100 lines of code that sets up a HTTP request first, then executes the request in a future. The 100 lines of code are just instructions that take up CPU time (however brief) on a single core whether they're running in the event loop or in a background thread pool. Is it worth the thread switching cost to move these 100 lines to a background thread pool? Is it even worth the overhead of moving this code into a future instead of simply executing it in the task?
I filed tokio#2414 after someone mentioned this issue on discord.
@Darksonn I want to clarify something, if I have code that is not necessarily compute heavy but still blocking e.g. a GUI framework or a game loop that does not use Rust's native async features, what is the recommended way of interfacing with Tokio?
GUI event loops or game loops should go on their own threads and use channels to communicate.
Normal threads or Tokio spawn_blocking threads?
Normal threads. The latest version of Tokio actually had some documentation improvements on spawn_blocking
that talks about this:
The
spawn_blocking
function is intended for non-async operations that eventually finish on their own. If you want to spawn an ordinary thread, you should usethread::spawn
instead.
The documentation says:
This makes sense, but because the Tokio
Runtime
uses a thread pool, does this really apply? The documentation also says:Does this not apply? If Tokio is not suited for running CPU-intensive tasks, how should they be run? On a separate thread pool and synchronized to the
Runtime
via channels? Chopped up into many tiny steps? These solutions seem cumbersome and inefficient, but if it's important to keep tasks fast, I'm not sure what else to do.Edit: I just read about Yielding. Is this the recommended way to do things? Are there guidelines about how tasks should be broken up? (Run in too many steps, and there might be too much overhead. Run in too few steps, and you block other tasks.) What about tasks that can't be yielded? (E.g. the computation happens in an external crate.)