| ▲ | throw-qqqqq 2 days ago |
| > But all it did was show us that async code just plain sucks compared to green thread code that can just block, instead of having to do the async dances. I take so much flak for this opinion at work, but I agree with you 100%. Code that looks synchronous, but is really async, has funny failure modes and idiosyncracies, and I generally see more bugs in the async parts of our code at work. Maybe I’m just old, but I don’t think it’s worth it. Syntactic sugar over continuations/closures basically.. |
|
| ▲ | lacker 2 days ago | parent | next [-] |
| I'm confused, I feel like the two of you are expressing opposite opinions. The comment you are responding to prefers green threads to be managed like goroutines, where the code looks synchronous, but really it's cooperative multitasking managed by the runtime, to explicit async/await. But then you criticize "code that looks synchronous but is really async". So you prefer the explicit "async" keywords? What exactly is your preferred model here? |
| |
| ▲ | throw-qqqqq 2 days ago | parent | next [-] | | First, I don’t mean to criticize anything or anyone. People value such things subjectively, but for me the async/sync split does no good. Goroutines feel like old-school, threaded code to me. I spawn a goroutine and interact with other “threads” through well defined IPC. I can’t tell if I’m spawning a green thread or a “real” system thread. C#’s async/await is different IMO and I prefer the other model. I think the async-concept gets overused (at my workplace at least). If you know Haskell, I would compare it to overuse of laziness, when strictness would likely use fewer resources and be much easier to reason about. I see many of the same problems/bugs with async/await.. | | |
| ▲ | thomasahle 2 days ago | parent | next [-] | | Comparing to Haskell, I think of "async" as the IO monad. It's nice to have all code that does IO flagged explicitly as such. | |
| ▲ | raxxorraxor 2 days ago | parent | prev | next [-] | | > I think the async-concept gets overused (at my workplace at least). Problem is it that it self reinforces and before you look every little function is suddenly async. The irony is that it is used where you want to write in a synchronous style... | | |
| ▲ | carlmr 2 days ago | parent [-] | | Yep, this is my biggest gripe with explicit async, all of a sudden a library that needn't be async forces me to use async (and in Rust forces me to use their async implementation), just because the author felt like async is a nice thing to try out. |
| |
| ▲ | eddd-ddde 2 days ago | parent | prev | next [-] | | Wouldn't the old school style be more like rust async? Simple structs that you poll whenever you need to explicitly. No magic code that looks synchronous but isn't. | | |
| ▲ | tptacek 2 days ago | parent [-] | | No, Rust async is new-school colored-functions concurrency. | | |
| ▲ | pkolaczk 2 days ago | parent [-] | | The parent comment is right. Rust async is simple state automata structs you can poll explicitly with no magic. Async/await is just some syntactic sugar on top of that, but you don’t have to use it. An obvious advantage of doing it that way is you don’t need any runtime/OS-level support. Eg your runtime doesn’t need to even have a concept of threads. It works on bare metal embedded. Another advantage is that it’s fully cooperative model. No magic preemption. You control the points where the switch can happen, there is no magic stuff suddenly running in background and messing up the state. | | |
| ▲ | jpc0 2 days ago | parent | next [-] | | Have you actually tried to implement async in rust from the ground up. It is nothing like what you just described | |
| ▲ | tptacek 2 days ago | parent | prev [-] | | I didn't say it was good or bad. I said it's new-school colored functions. |
|
|
| |
| ▲ | sfn42 2 days ago | parent | prev [-] | | I always find it strange how people complain about features when the real problem is that they simply don't like how people use the feature. Async in C# is awesome, and there's nothing stopping you from writing sync code where appropriate or using threads if you want proper multi threading. Async is primarily used to avoid blocking for non-cpu-bound work, like waiting for API/db/filesystem etc. If you use it everywhere then it's used everywhere, if you don't then it isn't. For a lot of apps it makes sense to use it a lot, like in web apis that do lots of db calls and such. This incurs some overhead but it has the benefit of avoiding blocked threads so that no threads sit idle waiting for I/O. You can imagine in a web API receiving a large number of requests per second there's a lot of this waiting going on and if threads were idle waiting for responses you wouldn't be able to handle nearly as much throughout. |
| |
| ▲ | throwaway81523 2 days ago | parent | prev [-] | | No, goroutines are preemptive. They avoid async hazards though of course introduce some different ones. | | |
| ▲ | Yoric 2 days ago | parent [-] | | To be fair, it depends on the version of Go. Used to be well-hidden cooperative, these days it's preemptive. | | |
|
|
|
| ▲ | kibwen 2 days ago | parent | prev | next [-] |
| > Code that looks synchronous, but is really async, has funny failure modes and idiosyncracies But this appears to be describing languages with green threads, rather than languages that make async explicit. |
| |
| ▲ | pclmulqdq 2 days ago | parent [-] | | Without the "async" keyword, you can still write async code. It looks totally different because you have to control the state machine of task scheduling. Green threads are a step further than the async keyword because they have none of the function coloring stuff. You may think of use of an async keyword as explicit async code but that is very much not the case. If you want to see async code without the keyword, most of the code of Linux is asynchronous. | | |
| ▲ | Dylan16807 2 days ago | parent | next [-] | | Having to put "await" everywhere is very explicit. I'd even say it's equally explicit to a bunch of awkward closures. Why do you say it's less? | | |
| ▲ | pclmulqdq 2 days ago | parent | next [-] | | It's explicit that the code is async, but how the asynchrony happens is completely implicit with async/await, and is managed by a runtime of some kind. Kernel-style async code, where everything is explicit: * You write a poller that opens up queues and reads structs representing work * Your functions are not tagged as "async" but they do not block * When those functions finish, you explicitly put that struct in another queue based on the result Async-await code, where the runtime is implicit: * All async functions are marked and you await them if they might block * A runtime of some sort handles queueing and runnability Green threads, where all asynchrony is implicit: * Functions are functions and can block * A runtime wraps everything that can block to switch to other local work before yielding back to the kernel | | |
| ▲ | lstodd 2 days ago | parent [-] | | > Green threads, where all asynchrony is implicit: which are no different from app POV from kernel threads, or any threads for that matter. the whole async stuff came up because context switch per event is way more expensive than just shoveling down a page of file descriptor state. thus poll, kqueue, epoll, io_uring, whatever. think of it as batch processing |
| |
| ▲ | throw-qqqqq 2 days ago | parent | prev | next [-] | | > Why do you say it's less Let me try to clarify my point of view: I don’t mean that async/await is more or less explicit than goroutines. I mean regular threaded code is more explicit than async/await code, and I prefer that. I see colleagues struggle to correctly analyze resource usage for instance. Someone tries to parallelize some code (perhaps naiively) by converting it to async/await and then run out of memory. Again, I don’t mean to judge anyone. I just observe that the async/await-flavor has more bugs in the code bases I work on. | | |
| ▲ | curt15 2 days ago | parent [-] | | >I don’t mean that async/await is more or less explicit than goroutines. I mean regular threaded code is more explicit than async/await code, and I prefer that. More explicit in what sense? I've written both regular threaded Python and async/await Python. Only the latter shows me precisely where the context switches occur. |
| |
| ▲ | throwawayffffas 2 days ago | parent | prev [-] | | Because it hides away the underlying machinery. Everything is in a run loop that does not exist in my codebase. The context switching points are obvious but the execution environment is opaque. At least that's how it looks to me. | | |
| ▲ | toast0 2 days ago | parent | next [-] | | The problem isn't that it hides away the machinery. The problem is that it hides some things, but not everything. Certainly a lot of stuff hides behind await/async. But as a naive developer who is used to real threads and green threads, I expected there would be some wait to await on a real thread and all the async stuff would just happen... but instead, if you await, actually you've got to be async too. If you had to write your async code where you gave an eventloop a FD and a callback to run when it was ready, that would be more explicit, IMHO... but it would be so wordy that it would only get used under extreme duress... I've worked on those code bases and they can do amazing things, but if there's any complexity it quickly becomes not worth it. Green threads are better (IMHO), because they actually do hide all the machinery. As a developer in a language with mature green threads (Erlang), I don't have to know about the machinery[1], I just write code that blocks from my perspective and BEAM makes magic happen. As I understand it, that's the model for Java's Project Loom aka Java Green Threads 2: 2 Green 2 Threads. The first release had some issues with the machinery, but I think I read the second release was much better, and I haven't seen much since... I'm not a Cafe Babe, so I don't follow Java that closely. [1] It's always nice to know about the machinery, but I don't have to know about it, and I was able to get started pretty quick and figure out the machinery later. | | |
| ▲ | worthless-trash 2 days ago | parent [-] | | I don't know who you are, but thanks.. My beam code goes brrrr.. so fast, much async, so reliable, no worries. |
| |
| ▲ | ForHackernews 2 days ago | parent | prev [-] | | I don't understand this criticism. The JVM is opaque, App Engine is opaque, Docker is opaque. All execution environments are opaque unless you've attached a debugger and are manually poking at the thing while it runs. | | |
| ▲ | pclmulqdq 2 days ago | parent [-] | | Some are more opaque than others. | | |
| ▲ | lstodd 11 hours ago | parent [-] | | If those persist in opaqueness, say "confess, or I fetch Ghidra". If even this does not help, rm -rf is your friend. |
|
|
|
| |
| ▲ | vova_hn 2 days ago | parent | prev | next [-] | | > Green threads are a step further than the async keyword because they have none of the function coloring stuff. I would say that green threads still have "function coloring stuff", we just decided that every function will be async-colored. Now, what happens if you try to cross an FFI-border and try to call a function that knows nothing about your green-thread runtime is an entirely different story... | |
| ▲ | throw-qqqqq 2 days ago | parent | prev [-] | | This is exactly what I mean. Thank you for explaining much more clearly than I could. > none of the function coloring stuff And it’s this part that I don’t like (and see colleagues struggling to implement correctly at work). |
|
|
|
| ▲ | Uptrenda 2 days ago | parent | prev | next [-] |
| I'm a person who wrote an entire networking library in Python and I agree with you. The most obvious issue with Python's single-threaded async code is any slow part of the program delays the entire thing. And yeah -- that's actually insanely frigging difficult to avoid. You write standard networking code and then find out that parts you expected to be async in Python actually ended up being sync / blocking. DESPITE THAT: even if you're doing everything "right" (TM) -- using a single thread and doing all your networking I/O sequentially is simply slow as hell. A very very good example of this is bottle.py. Lets say you host a static web server with bottle.py. Every single web request for files leads to sequential loading, which makes page load times absolutely laughable. This isn't the case for every python web frame work, but it seems to be a common theme to me. (Cause: single thread, event loop.) With asyncio, the most consistent behavior I've had with it seems to be to avoid having multiple processes and then running event loops inside them. Even though this approach seems like its necessary (or at least threading) to avoid the massive down sides of the event loop. But yeah, you have to keep everything simple. In my own library I use a single event loop and don't do anything fancy. I've learned the hard way how asyncio punishes trying to improve it. It's a damn cool piece of software, just has some huge limitations for performance. |
| |
| ▲ | fulafel 2 days ago | parent [-] | | bottle.py is a WSGI backed framework, right? So it's agnostic about whether you are running with threads, fork, blocking single thread IO, gevent, or what. | | |
| ▲ | Uptrenda 2 days ago | parent [-] | | Umm, acktually... (the default server is non-threaded and sequential. It was an example.) | | |
|
|
|
| ▲ | larusso 2 days ago | parent | prev | next [-] |
| async is like a virus. I think the implementation in js and .NET is somewhat ok’ish because your code is inside an async context most of the time. I really hate the red / blue method issues where library functions get harder to compose. Oh I have a normal method because there was no need for async. Now I change the implementation and need to call an async method. There are ways around this but more often than not will you change most methods to be async. To be fair that also happens with other solutions. |
| |
| ▲ | DanielHB 2 days ago | parent [-] | | It is not nearly as much of a problem in JS because JS only has an event loop, there is no way to mix in threads with async code because there are no threads. Makes everything a lot simpler and a lot of the data structures a lot faster (because no locks required). But actual parallelization (instead of just concurrency) is impossible[1]. A lot of the async problems in other languages is because they haven't bought up into the concept fully with some 3rd party code using it and some don't. JS went all-in with async. [1]: Yes I know about service workers, but they are not threads in the sense that there is no shared memory*. It is good for some types of parallelization problems, but not others because of all the memory copying required. [2]: Yes I know about SharedArrayBuffer and there is a bunch of proposals to add support for locks and all that fun stuff to them, which also brings all the complexity back. | | |
| ▲ | _moof 2 days ago | parent [-] | | In my less charitable moments, I've wondered if the real reason Python has async/await is because people coming to it from JavaScript couldn't be arsed to learn a more appropriate paradigm. |
|
|
|
| ▲ | markandrewj 2 days ago | parent | prev [-] |
| I can tell you guys work with languages like Go, so this isn't true for yourselves, but I usually find it is developers that only ever work with synchronous code who find async complicated. Which isn't surprising, if you don't understand something it can seem complicated. My views is almost that people should learn how to write async code by default now. Regardless of the language. Writing modern applications basically requires it, although not all the time obviously. |
| |
| ▲ | Yoric 2 days ago | parent | next [-] | | Hey, I'm one of the (many, many) people who made async in JavaScript happen and I find async complicated. | | |
| ▲ | markandrewj 2 days ago | parent [-] | | Hey Yoric, I do not want to underplay what it is like to work with async, but I think there has been a lot of improvements to make it easier, especially in JavaScript/ECMAScript. It is nice not to have to work directly with promises in the same way that was required previously. The language has matured a lot since I started using in Netscape Navigator (I see you formerly worked at Mozilla). I think coding can be complicated in general, although it shouldn't have to be. I think having a mental model for async from the start can be helpful, and understanding the difference between blocking and non blocking code. A lot of people learned writing synchronous code first, so I think it can be hard to develop the mental model and intuit it. |
| |
| ▲ | ErikBjare 2 days ago | parent | prev [-] | | I have no problem with async in JS or Rust, but async in Python is a very different beast, and like many people in this thread I do my best to avoid the fully loaded footgun altogether. Writing maintainable Python basically requires avoiding it, so I strongly disagree with "regardless of language". | | |
| ▲ | markandrewj 2 days ago | parent [-] | | Maybe, but I wouldn't go back to Python 2 without async. It has also improved over time in Python. I have also had success using async in Python. I do understand what the article talks about however. Understanding the difference between blocking and non-blocking code is also a concept relevant to Python. In Node it's one of the concepts you are first introduced to, because Node is single threaded by default. I also understand in Go and other languages there are different options. https://nodejs.org/en/learn/asynchronous-work/overview-of-bl... I will agree with what some is said a above, BEAM is pretty great. I have been using it recently through Elixir. |
|
|