Live data from Hacker News

How to think about async/await in Rust

cliffle.com

121–130 of 268 posts

Re: How to think about async/await in Rust

#121

Earlier quoted context omitted.

> Threads are a resource hog. Not really on any decent operating system, but if they are too heavy, there's still fibers aka green-threads aka stack-switching (which at least on Windows are an operating system primitive - but can be implemented in user code on any system that gives you direct access to the CPU stack and registers). I doubt that the async-await state machine code transformation which 'slices' sequenti…

Userspace fibers (no clue about Windows fibers) still have the blocking IO problem. If your fiber calls read() but there's no data and read blocks for a few minutes, until the next message is received, no other fibers can be scheduled on that thread in the meantime. With async, the task just gets suspended, something like epoll gets called with info about all the suspended tasks, and the thread unblocks once any task…

If the blocking function would be fiber-aware, and yield execution back to the fiber runtime until the (underlying) async operation has completed, it would "just work". One could most likely write their own wrapper functions which use the Windows "overlapping IO" functions (those just have a completion callback if I remember right - PS or maybe completion Event?)

Not possible with the C stdlib IO functions though (that's why it would be nice to have optional async IO functions with completion callback in the C stdlib)

PS: just calling a blocking read in async/await code would have the same effect though, you need an "async/await aware" version of read()

Re: How to think about async/await in Rust

#122

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

Look at this program: val another_path = await readFile(path); val data = await readFile(another_path); console.log(data.length); How would you do that using threads?

Exactly like that, but using blocking functions. Then I run the code in a worker pool or in a greenlet runtime. The kernel and runtime take care of putting threads to sleep when they hit blocking IO.

Re: How to think about async/await in Rust

#123

Earlier quoted context omitted.

> Threads are a resource hog. Not really on any decent operating system, but if they are too heavy, there's still fibers aka green-threads aka stack-switching (which at least on Windows are an operating system primitive - but can be implemented in user code on any system that gives you direct access to the CPU stack and registers). I doubt that the async-await state machine code transformation which 'slices' sequenti…

Userspace fibers (no clue about Windows fibers) still have the blocking IO problem. If your fiber calls read() but there's no data and read blocks for a few minutes, until the next message is received, no other fibers can be scheduled on that thread in the meantime. With async, the task just gets suspended, something like epoll gets called with info about all the suspended tasks, and the thread unblocks once any task…

if your async task performs a raw read it also will block. In the coroutine case you of course need to call a read wrapper that allows for user mode scheduling. That can literally be the same function you use for async. Coroutines also allow library interposition tricks that transparently swap a blocking read with one that returns control to the event loop, so in principle existing blocking code need not change. Libpth did something like that for example. YMMV.

Re: How to think about async/await in Rust

#124
post #3

Earlier quoted context omitted.

Your comment seems to be conflating concurrency with parallelism. JS doesn't have any language-level abstractions for parallelism (async or not) but you do have Web Workers[0] and process forking (depending on runtime) to get actual parallel programming. JS async deals with concurrency, not parallelism. Threads are the opposite: They are interfaces for parallel programming and their use is orthogonal to how your appl…

> Your comment seems to be conflating concurrency with parallelism. No, it really doesn't. I mention both concurrency and parallelism, and their main difference. > Threads are the opposite: They are interfaces for parallel programming No, they are not. Threads can do both. When waiting for an i/o bound operation, a thread can simply sleep. Added bonus: A thread basd implementation supports io bound concurrency and cp…

> It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it

If the comment is not conflating concurrency and parallelism as you say, mind expanding on this part?

Re: How to think about async/await in Rust

#125
post #41

Earlier quoted context omitted.

> Haven't done too much async Rust yet but I don't think it solved this issue from what I've seen. In Rust an async function is really just a const fn that synchronously only constructs and returns a state machine struct that implements the Future trait. So async fn foo(x: i32) { } essentially desugars to const fn foo(x: i32) -> FooFuture { FooFuture { x } } struct FooFuture { x: i32 } // technically it's an enum mod…

It's not const (const fn has a very specfic meaning in Rust), but other than that you're correct.

It is "const enough", as in, there is nothing preventing it from being called at compile time, in fact with nightly features (and no changes to the async fn), you can call it at compile time just fine. I also put const fn there to emphasize that it really can't do all that much beside constructing the state machine.

Re: How to think about async/await in Rust

#126

Earlier quoted context omitted.

You definitely need a mutex here (or use atomics), otherwise you have a race condition

Where exactly? Can you point me to the data race? Consider that the thread constructor call happens-before the thread start and the thread termination happens-before the join call returns.

Ah sorry, I missed that you only spawn a single thread. Mea culpa!

Re: How to think about async/await in Rust

#127

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

> Code written using threads is, at least to me, much more readable and easier to reason about.

It's “easier” because it lies to you and makes you assume that everything is sequential, but it's not, and sometime that “everything is sequential” abstraction is leaky, and you can't really see what's going on without diving to the bottom of every functions.

I've been accustomed so much to the transparency of async/await, that I now whish we had the same kind of thing for functions using blocking syscalls (for instance you could annotate the function with the `blocking` keyword and need to use `block` to call it) so you known you need to spawn a new thread if you don't want to wait until the completion of some I/O-bound function.

Re: How to think about async/await in Rust

#128
post #49

Earlier quoted context omitted.

Not in Rust if you use something like Tokio.

I'm pretty sure this program has no concurrency even if you port it to Rust and use Tokio.

This particular one, yes. However, most programs are a bit more complicated than that. That and Async is mostly used for io operations. In which case, the value here is not from non-blocking operations but from freeing up the CPU while this operation finishes execution. You can think of that as another form of executing this program on a separate thread (since the CPU will be freed to do other tasks).

Re: How to think about async/await in Rust

#129
post #78

Earlier quoted context omitted.

Maybe I'm misunderstanding what you mean by "It began in C#", but F# introduced async about five years before C# 5 was released.

async in F# is not a language feature, it’s a library that leverages F# computation expressions (monads). It’s also possible to do async-like behaviour - without the async/await language feature - in C# using LINQ; so you could argue C# has had the capability (like F#) since LINQ was released. But, I believe C# was the first mainstream language to implement the async/await method-splitting coroutines state-machine (a…

Async is a special case of continuations. If you have fist class continuations (and monads do notation in practice gives you that), you hardly need async as a language feature.

Re: How to think about async/await in Rust

#130
post #128

Earlier quoted context omitted.

I'm pretty sure this program has no concurrency even if you port it to Rust and use Tokio.

This particular one, yes. However, most programs are a bit more complicated than that. That and Async is mostly used for io operations. In which case, the value here is not from non-blocking operations but from freeing up the CPU while this operation finishes execution. You can think of that as another form of executing this program on a separate thread (since the CPU will be freed to do other tasks).

Doing a blocking read also frees your CPU to do other tasks!
Post reply on HN