6 ms·
What Is Zig's “Colorblind” Async/Await?
- gfs 6y agoIs there any difference in the `.evented` example and the example that explicitly uses the `async` and `await` keywords?
- tingletech 6y agothe async await example is python, the rest of the code is zig
- gfs 6y agoSorry, I meant this one: https://kristoff.it/blog/zig-colorblind-async-await/#expressing-concurrency https://kristoff.it/blog/zig-colorblind-async-await/#express...
- kristoff_it 6y agoThe two examples are perfectly equivalent.
- howeyc 6y agowhen set to .blocking > The function is no longer async, and in fact both keywords basically become no-ops That sounds crazy to me, if I use a keyword I expect it to behave the same everywhere I use it. I hope I misunderstood, and that setting come kind of global const can't "disable" async for one library, but still have it enabled when used somewhere else. I would have to keep track of all these various consts to know my app is/isn't actually doing async tasks, even though I've explicitly used the keyword.
- kristoff_it 6y agoIt's not a switch for libraries, it's a global project-wide switch. It enables or disables evented I/O for your whole application. Libraries can read that setting and specialize at compile-time appropriately.
- Matthias247 6y agoWhat I find a little bit surprising with this is that the behavior of a library can be changed outside of its view. E.g. if the library requires to always run in an evented mode (because it needs to spawn many small tasks), then the application developer settings things into threaded mode might prevent the library from working properly. Similar questions might arise around cancellation and timeouts, which are different to perform in an async/evented environment than a synchronous one. In most other worlds the problem is "solved" by declaring a subsystem of the application as async. And only that subsystem is running one or more eventloops. That obviously introduces a different set of issues, but it isolates parts of a program against others. I'm really curious on how Zigs approach will work out in any bigger applications where libraries from various parties would get used.
- kristoff_it 6y ago> What I find a little bit surprising with this is that the behavior of a library can be changed outside of its view. E.g. if the library requires to always run in an evented mode (because it needs to spawn many small tasks), then the application developer settings things into threaded mode might prevent the library from working properly. In that case you would add in such library a line like this: if (!std.io.is_async) @compileError("LIB doesn't support blocking mode"); > I'm really curious on how Zigs approach will work out in any bigger applications where libraries from various parties would get used. That's an open question. In general Zig is more like C than C++ also when it comes to expectations from libraries. We'll see where this will get us.
- Matthias247 6y ago> In that case you would add in such library a line like this: That's certainly an approach to make sure libraries run in the correct environment. But it still doesn't seem to enable use-cases where you run an eventloop on some threads and blocking IO (or just blocking on the eventloop) on other threads, which are often found in bigger programs that are made up of a lot of heterogenous libraries. One could say "you might not need that - just run everything async". But since we are talking about a system programming language where libraries might be exported and used in different projects I'm not sure if that lack of flexbility won't hurt long term.
- yellowapple 6y agoYeah, per the docs¹ the article doesn't seem to be accurate in that part; none of the doc's examples mention anything about setting something to .blocking v. .evented. The keywords might not actually achieve meaningful concurrency due to implementation details (i.e. a coroutine stuck on a blocking operation, which might be what that particular global variable controls), but they definitely ain't no-ops from what I understand. ---- ¹: https://ziglang.org/documentation/master/#Async-Functions https://ziglang.org/documentation/master/#Async-Functions
- kristoff_it 6y agoThe 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.
- lalaithion 6y agoSetting that global const is the same as choosing between various top-level future executors. It can only be set once in a project, and it's morally equivalent to changing `asyncio.run()` to some other function in the python example. It just allows library writers to write async functions and have the end user choose which async impl to use.
- skybrian 6y agoIt seems like this should work so long as popular libraries support both modes. That should be easier to do in Zig, but if there is a popular library that can only support one mode or the other, or worse, two popular libraries that only support opposite modes, it will constrain any downstream dependencies as well, and you have colored functions again. But will you get an error at compile time, or will the system just deadlock? This might be something that just needs to be handled socially, by convention, much like you're not supposed to use too much unsafe code in Rust, and you should make sure that public API's are safe.
- atombender 6y agoZig's use of "async" as a call-site modifier is exactly how I've long wished async/await had been done in languages like JavaScript and Rust. It completely fixes the "color" problem. I brought it up on HN once, and apparently this was discussed as an option for Rust, but for whatever reason, people didn't like it. To be fair, Rust has certain design requirements that might not be compatible with such a system.
- j-pb 6y agoThe reason is that it creates completely unexpected bugs, like deadlocks. Static function coloring based in asyncronicity is a feature not a bug. Making it "dynamic" is like saying: "Yeah we've decided that if you just treat an Option as a value, we will let you do it, and we'll also treat every Option boxing inside your map as a noop."
- fooyc 6y agoThis looks like Go: The code is synchronous, but I/O uses polling under the hood, so that the programmer gets the best of both worlds. When a Goroutine would block, it's parked by the scheduler, thus allowing an other Goroutine to execute. And it's not only about I/O: This works for any syscall, or even for CPU-bound computations. There is no clever trick from the compiler to allow this: There is just a hook to the scheduler in low-level I/O code as well as before and after syscalls.