4 ms·
>If you start over, there's no reason any blocking API should really BLOCK your program. Your functions can look completely synchronous, but yield to the event
by simias 5y ago
>If you start over, there's no reason any blocking API should really BLOCK your program. Your functions can look completely synchronous, but yield to the event loop when they invoke a blocking API.
That implies a level of sophistication and abstraction that simply doesn't exist today, and probably won't for a while.
For this to work you either need an ultra-sophisticated compiler what will transparently auto-async your code at compile time (as realistic as a unicorn at this point) or a heavy runtime which will have performance implications.
So in the meanwhile I agree with TFA that coloured functions is probably the pragmatic choice if you want pay-for-what-you-use async right now.
Personally my main issue with async is that it seems to me that a generation of devs who grew up on JavaScript see it as the Alpha and Omega, the one and only way of handling any sort of parallel or event-driven system, not considering other approaches such as threads and/or polling. This sometimes results in spaghetti code that I personally find over-engineered.
To give you an example I hacked on rbw a week ago, a Rust command line client for Bitwarden. It's a good program, but it's completely built with async code and I genuinely don't get it given that it's almost entirely synchronous in practice (you query an entry, you enter your passphrase, you get a result). As a result I found it a lot harder to follow the flow of the program and understand how it all fit together. But hey, I eventually got it and managed to fix the issue I was debugging, so it's not the end of the world.
- meowface 5y ago>That implies a level of sophistication and abstraction that simply doesn't exist today, and probably won't for a while. >For this to work you either need an ultra-sophisticated compiler what will transparently auto-async your code at compile time (as realistic as a unicorn at this point) or a heavy runtime which will have performance implications. I may be misunderstanding this point, but if I'm not, it seems like gevent managed to do this for Python a decade ago and it still works well today. I still use gevent for new code because it gracefully sidesteps this function-coloring problem. (I know people don't normally associate "monkey-patch" with "graceful", but they somehow made it work. I guess this falls under the "heavy runtime" approach, but it doesn't feel heavy at all.)
- simias 5y agoRight, maybe "heavy" was a bit too strong, I was more thinking from the point of view or Rust which aims to have a small, mostly optional runtime.
- lmm 5y agogevent is difficult to use when interoperating with code outside its runtime (e.g. if you have a callback that gets called from a C library) and you lose control over where you yield, so you'll never get as high throughput as blocking code in the scenarios where that's appropriate. It's effectively just making all your functions async, like JavaScript or indeed Erlang.
- meowface 5y ago>gevent is difficult to use when interoperating with code outside its runtime (e.g. if you have a callback that gets called from a C library) True, but is this not also the case when using Python's native async/await functionality? The difference is that async/await doesn't even work with previous pure-Python code which performs any I/O, either. There are thousands of ordinary third-party libraries which basically have to be tossed out the window and rewritten from scratch, despite none of them using any C extensions. gevent works well with almost all of those old libraries.
- lmm 5y ago> True, but is this not also the case when using Python's native async/await functionality? Yes indeed. > The difference is that async/await doesn't even work with previous pure-Python code which performs any I/O, either. There are thousands of ordinary third-party libraries which basically have to be tossed out the window and rewritten from scratch, despite none of them using any C extensions. Hmm, yeah. I sometimes wonder about a language style where functions are monadic by default, and I guess being able to monkeypatch the internals is a way to achieve the same thing - rather than introducing a syntax for a "programmable semicolon", we can just monkeypatch the actual semicolon. Introducing effectfulness into code that wasn't written to accommodate it worries me, but I guess gevent usually works in practice. I guess a language like Frank is a more principled way of doing that kind of thing.
- geocar 5y ago> >If you start over, there's no reason any blocking API should really BLOCK your program. Your functions can look completely synchronous, but yield to the event loop when they invoke a blocking API. > That implies a level of sophistication and abstraction that simply doesn't exist today, and probably won't for a while. This sounds like a description of UNIX and was true from the very first versions: a blocking call would trap the system, select a process task that can continue and jump to that instead. At the language level, I'm pretty sure we had this in the 1980s in Forth, and I don't think it was considered new even then (although I don't have a reference handy); libdill does this for C programs. Or am I misunderstanding what you're saying?
- lmm 5y agoMultiprocessing (or multithreading, which is the unix model is much the same thing) is even heavier than a green-threading runtime. Instead of swapping out little task handlers / continuations you're swapping out whole address spaces.
- simias 5y agoSo there are two things, there's OS-provided event handling (poll, blocking syscall that schedule another task, OS threads) and there's coroutines (libdill and friends). OS-level async is what I accuse some devs to underuse, preferring language-level async constructs. I've read a lot of frankly FUD and cargo culting about how inefficient things like pthreads are. Let's not even talk about pre-fork parallelism which is probably the simplest way to use multiple cores when dealing with independent tasks but hardly anybody seems to use these days. I wonder if most devs even realize that it's an option. In my experience coroutines were never really popular in the C world simply because without GC or at least a borrow checker it's just very hard to write correct code. Avoiding memory violations in C is tricky, avoiding memory violation in async C is asking a lot. Rust does a lot better here thanks to borrow checking but it still leads to, IMO, very complicated code.
- akiselev 5y ago> Let's not even talk about pre-fork parallelism which is probably the simplest way to use multiple cores when dealing with independent tasks but hardly anybody seems to use these days. I wonder if most devs even realize that it's an option. Speaking from experience: they don't. I recently picked up a Linux kernel development book [1] and after four or five chapters I was able to eliminate several bottlenecks and eliminate latency edge cases in personal projects with a greater understanding of the scheduler algorithm and process priority/affinity/yields. I think the problem is that the vast majority of developers work on top of cross-platform runtimes that hide any detail that can't be easily implemented for all operating systems. It's been more than a decade since I've used Unix sockets directly in any language and I don't how to set SO_REUSEPORT in Rust. I'd probably have to implement it using `TCPListener::as_raw_fd()` and `unsafe libc::setsockopt`? It's just easier stay sane within the bumpers. [1] https://www.amazon.com/Linux-Kernel-Development-Robert-Love/dp/0672329468/ https://www.amazon.com/Linux-Kernel-Development-Robert-Love/...
- tomp 5y ago> a level of sophistication and abstraction that simply doesn't exist today > you need an ultra-sophisticated compiler (as realistic as a unicorn at this point) Isn't what Zig does, described in the post linked by a few other comments [1], basically this? [1] https://kristoff.it/blog/zig-colorblind-async-await/ https://kristoff.it/blog/zig-colorblind-async-await/
- dnautics 5y agoYes, and it works quite well. I think it works well because zigs async requires you to think what is actually going on with the data structures at the low level (you have to understand what a frame is, that's a "new" low level construct on the level of "pointer" or "function"), once you get that and understand the control flow it is quite easy to see what's happening.
- audunw 5y ago> For this to work you either need an ultra-sophisticated compiler what will transparently auto-async your code at compile time (as realistic as a unicorn at this point) or a heavy runtime which will have performance implications. Depends on how much of it you want to solve automatically. The Zig compiler does essentially auto-async your functions. So I think you can write a whole lot of synchronous code, and then just have a single "async" at the top to have it yield to the event loop whenever it hits blocking IO. So I think you can solve 90% of that problem without being ultra-sophisticated.
- cube2222 5y agoThis is quite literally how Go works and has been working for years. Automatic yield points and a proxy to syscalls are used for that. It's also still plenty fast (as opposed to Erlang, which was mentioned in other comments).