4 ms·
The docs are about the mechanics of how async frames work with examples of how the control flow jumps based on manual scheduling. The top level constant intera
by kristoff_it 6y ago
The docs are about the mechanics of how async frames work with examples of how the control flow jumps based on manual scheduling.
The top level constant interacts with the standard library to instantiate an event loop and add non-blocking flags to file descriptors etc.
Try the code out, use strace to see the syscalls being made and you will confirm that my article is accurate.
- AndyKelley 6y agoYou can also watch the linked video which demos the strace. https://youtu.be/zeLToGnjIUM?t=546 https://youtu.be/zeLToGnjIUM?t=546 (9:06 to 15:24)
- howeyc 6y agoOh, I see. So blocking still executes asynchronously (perhaps spawning a thread or something, so the program can continue on), but setting eventing will use an event-based mechanism?
- dathinab 6y ago> (perhaps spawning a thread or something, so the program can continue on) No as far as I understand blocking just blocks. Which is the problematic part. > The function is no longer async, and in fact both keywords basically become no-ops, but the point is that you can express concurrency even if you’re not able to take advantage of it. (Emphasis done by me) For many cases this is fine as "async" could just have completed one the first poll which we could just have done before the first yield. But the problem is if you try to do something like calling two functions async which needs synchronization between them to complete. Something you generally should really avoid. But sometimes this is the only sane way to do certain things in complex systems. (But then it might make sense to simplify such a system or split it apart). So I would say for many use-cases including all normal web-servers Zig's solutions is not just good but preferable. But to do some things it's bad. Through it's just a question to add some escape hatches for the view cases where it matters or so. Anyway spawning a thread on every `async` call if `.blocking` is used would be really bad.
- AndyKelley 6y agoOne could do the synchronization thing with zig's async await, it would just cause the code to become a compile error in blocking I/O mode. The code author would then have the option to document that function / library / application as not supporting blocking I/O mode, or explicitly put an `if (std.io.io_mode == .blocking)` check there and provide a blocking implementation.
- yellowapple 6y agoThe point of contention/discussion here is less about whether or not the IO itself is blocking or non-blocking (the article's obviously accurate on that point, as strace would confirm). I was more talking about the assertion that "The function is no longer async, and in fact both keywords basically become no-ops"; at least per the docs they're definitely not no-ops, with "async" still returning something that's awaitable with "await", no? Like, that variable doesn't seem to have any impact on whether or not Zig itself has a concept of an event loop or of asynchronous functions; both still continue to exist, even if the libraries happen to be configured to perform blocking operations. A function is still "async" (albeit pointlessly so) even if it doesn't yield.
- AndyKelley 6y ago> A function is still "async" (albeit pointlessly so) even if it doesn't yield. No that's not correct: https://godbolt.org/z/5AMxy- https://godbolt.org/z/5AMxy-
- yellowapple 6y agoI feel like that demonstrates my point (though maybe I didn't communicate it well). Given a modified version of that example¹, with everything being apples-to-apples (identical input code minus the presence of async/await keywords, and sticking to a debug build instead of --release-fast so as to avoid Zig and/or LLVM optimizing away the semantic differences), there are subtle differences in the interaction between the generated 'add' and 'add2' (calling convention differences maybe? I strongly suspect you'd know more about what's going on with rax and rcx there...). These differences continue to be visible with --release-safe (though --release-fast and --release-small both optimize away those differences, which does indeed seem like a reasonable thing to do). That is: even in that example, the fact that add2() doesn't yield doesn't change the fact that Zig treats it differently (namely, I would guess, by assuming that it has the potential to yield and thus doing the necessary bookkeeping to handle that) depending on whether or not it's invoked as an async function. If async/await were indeed "no-ops" as the article asserts, then shouldn't the assembly outputs here be identical in debug and release-safe? The LLVM IR output for --release-safe is telling, too, since it's apparent that Zig's emitting IR specifically around an async frame (at least that's my impression from seeing all those getelementptrs on a @Frame(add2) -- which, come to think of it, do correspond to the extra rax/rcx manipulations in the async version). --release-fast even shows a difference on the IR level, though it's admittedly pretty inconsequential-looking (just an extra @llvm.debug.value call in the non-async version). EDIT: as another interesting note, these differences persist even with --single-threaded, which contradicts the documentation on Single Threaded Builds (which states that "The overhead of Async Functions becomes equivalent to function call overhead." -- I mean, I guess that's true with --release-fast or --release-small, but --single-threaded doesn't seem to have an effect there). ---- ¹: https://godbolt.org/z/_zfkeJ https://godbolt.org/z/_zfkeJ