tokio-rs / doc-push

Tokio doc blitz effort - A concerted effort to improve Tokio's documentation.
MIT License
50 stars 7 forks source link

How to perform computation heavy Tasks? #77

Open NPN opened 6 years ago

NPN commented 6 years ago

The documentation says:

Tasks must not perform computation heavy logic or they will prevent other tasks from executing. So don’t try to compute the fibonacci sequence as a task.

This makes sense, but because the Tokio Runtime uses a thread pool, does this really apply? The documentation also says:

[Y]ou can use a future to represent a wide range of events, e.g.:

  • 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.

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.)

carllerche commented 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.

kathampy commented 5 years ago

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?

Darksonn commented 4 years ago

I filed tokio#2414 after someone mentioned this issue on discord.

Immortalin commented 4 years ago

@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?

Darksonn commented 4 years ago

GUI event loops or game loops should go on their own threads and use channels to communicate.

Immortalin commented 4 years ago

Normal threads or Tokio spawn_blocking threads?

Darksonn commented 4 years ago

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 use thread::spawn instead.