-
Notifications
You must be signed in to change notification settings - Fork 249
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Start part 1: intro and concurrency chapter #230
Conversation
3327c77
to
8e29140
Compare
[^other]: There are some programming languages (or even libraries) which have concurrency which is managed within the program (without the OS), but with a pre-emptive scheduler rather than relying on cooperation between threads. Go is a well-known example. These systems don't require `async` and `await` notation, but have other downsides including making interop with other languages or the OS much more difficult, and having a heavyweight runtime. Very early versions of Rust had such a system, but no traces of it remained by 1.0. | ||
|
||
|
||
## Concurrency and Parallelism |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The line I've been using here is from Aaron Tuuron's PhD thesis: concurrency is a way of organizing work, parallelism is a resource. This line is reflected in std::thread::available_parallelism
, which intentionally deviates from C++'s hardware_concurrency
. Maybe that's a helpful framing to use here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a paragraph on this in the 'Enough silly examples, let's try to define things properly', I do like this framing a lot, definitely useful to add.
[^busywait]: There's another option which is that the thread can *busy wait* by just spinning in a loop until the IO is finished. This is not very efficient since other threads won't get to run and is uncommon in most modern systems. You may come across it in the implementations of locks or in very simple embedded systems. | ||
|
||
|
||
## Async programming |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This section focuses a fair bit on what happens under the hood for both threads and futures - but I believe it might be more important for users to emphasize what async allows you to do that threads can't:
- futures support arbitrary cancellation in a uniform way
- futures support arbitrary concurrent execution
- futures support user-defined control flow constructs like
join
and timeout which combine concurrency and cancellation to do interesting new things that non-async code can't do
I wrote more about this in my "why async?" post. Personally I find this more compelling than arguments around performance, since this describes actual new capabilities which can be used by users. Things like being able to apply timeouts to arbitrary computations in a standard way is a big deal - I think we should highlight that!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, I think that's right, but I don't think this is the right place. This chapter is meant to be a high-level summary of the different kinds of concurrency, and in a practical way, what is similar and what is different. In particular I want to be clear and precise about the assumptions from threaded concurrency which carry over to async. The performance difference mentioned here is present more as a consequence of that, rather than because I'm trying to 'sell' async. I've made a conscious decision to avoid any syntax here and focus on tasks rather than futures or about how async is represented in the source code. So I don't think talking about join, etc. would fit well here.
I think that cancellation and timeouts will only murky the waters for this chapter - there are equivalents for threads, they're just not as ergonomic as for async, and they don't tend to feature in libraries, etc. However, there isn't a fundamental reason for why not, and I think getting into the distinctions and details wouldn't be appropriate here.
I do have a little section in the intro which more about motivation for choosing async, and I'll expand that to include more of these things.
[^other]: There are some programming languages (or even libraries) which have concurrency which is managed within the program (without the OS), but with a pre-emptive scheduler rather than relying on cooperation between threads. Go is a well-known example. These systems don't require `async` and `await` notation, but have other downsides including making interop with other languages or the OS much more difficult, and having a heavyweight runtime. Very early versions of Rust had such a system, but no traces of it remained by 1.0. | ||
|
||
|
||
## Concurrency and Parallelism |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another thought here: the way I think about futures vs threads is that threads inherently couple concurrency and parallelism into a single construct. This is exemplified by things like hyper-threading.
Futures on the other hand intentionally decouple concurrency from parallelism. Concurrency after all is just a way of scheduling work, and with futures we don't have to make use of threads for this. This I feel like might be the most important property of futures, and I think is worth highlighting.
A framing I've started using is that tasks are not the async/await form of threads; it's more accurate to think of them as parallelizable futures. This framing does not match Tokio and async-std
's current task design; but both also have trouble propagating cancellation. See parallel_future
and tasks are the wrong abstraction for more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking quite a bit about this perspective, and my conclusion is that 'threads inherently couple concurrency and parallelism into a single construct' (which is the way I have been thinking as well) is not quite right. It's not inherent to threads, rather it is about the way they are conventionally used and configured. It's easy enough in most OS's to pin a thread to a CPU and/or specify which threads operate on the same cores (i.e., to separate out the parallelism), it's just that this is usually not surfaced in an ergonomic way in PLs. With futures/async, some degree of configuration is explicit in the code (e.g., join vs spawn) and some can be configured in the runtime.
A framing I've started using is that tasks are not the async/await form of threads; ...
While I agree this is a really useful framing, given that it's not how any major runtime works today and that the reader is likely to have some background with threads, I think the 'tasks are sort of threads' is going to be a more useful way to introduce readers here. I think I'll rely more on the 'tasks are parallelisable futures' framing when I introduce join, select, etc. later in the guide
Signed-off-by: Nick Cameron <nrc@ncameron.org>
cc #227, #228, #229
Draft of chapters 2 and 3.