15 ms·
From http://cr.openjdk.java.net/~rpressler/loom/loom/sol1_part1.html http://cr.openjdk.java.net/~rpressler/loom/loom/sol1_part1.h...: "Whereas the OS can suppo
by nu11ptr 4y ago
From http://cr.openjdk.java.net/~rpressler/loom/loom/sol1_part1.html http://cr.openjdk.java.net/~rpressler/loom/loom/sol1_part1.h...:
"Whereas the OS can support up to a few thousand active threads, the Java runtime can support millions of virtual threads. Every unit of concurrency in the application domain can be represented by its own thread, making programming concurrent applications easier. Forget about thread-pools, just spawn a new thread, one per task. You’ve already spawned a new virtual thread to handle an incoming HTTP request, but now, in the course of handling the request, you want to simultaneously query a database and issue outgoing requests to three other services? No problem — spawn more threads. You need to wait for something to happen without wasting precious resources? Forget about callbacks or reactive stream chaining — just block. Write straightforward, boring code. All the benefits threads give us — control flow, exception context, debugging flow, profiling organization — are preserved by virtual threads; only the runtime cost in footprint and performance is gone. There is no loss in flexibility compared to asynchronous programming because, as we’ll see, we have not ceded fine-grained control over scheduling."
This seems so obvious in hindsight as the "right way". I wonder why we went down that whole async/await craze with so many languages?
- pkulak 4y agoWell, this kinda thing isn't free. You need a rather large and complicated runtime to make it work. Fine for the JVM, which is already a large and complicated runtime, but it turns out that making a runtime like this optional is not really possible. So languages that don't have a runtime are stuck with async/await, which can be zero-cost. And, to be fair, languages like Erlang and Go went this route LONG ago.
- SemanticStrengh 4y agoExcept GraalVM bring support to almost any language
- thfuran 4y agoThat's some hefty sleight of hand.
- Comevius 4y agoGarbage collectors and preemptively scheduled virtual threads require a runtime. Even GraalVM Native Image has a large runtime for this reason. There is no free lunch here.
- SemanticStrengh 4y agoI'm just saying the cost of developing the runtime can now easily be mutualized between multiple languages
- pjmlp 4y agoAll high level languages have a runtime, the only difference among them is how big it is in practice. Even Assembly can be considered to have one, in case of microcoded CPUs.
- deleted 4y ago[deleted]
- moonchild 4y ago> languages like Erlang and Go went this route LONG ago So did java! It originally used green threads, before switching to exclusively kernel threads.
- orasis 4y agoasync/await is FAR less error prone than explicit threaded concurrency.
- jayd16 4y agoHaving more threads doesn't solve inter-thread concurrency like async/await does. With async/await you can achieve concurrency while ensuring there parallelism as well as is no parallelism (to avoid explicit critical sections). I don't think you could, for example, use this model of many virtual threads with a traditional GUI framework as designs are still predominately single threaded by nature. Said another way, cooperative scheduling has its place and will continue to. I'm excited to see some threaded UI designs shake out of this, though.
- kjeetgill 4y agoYea, it won't solve those things. I think the appeal for languages that already used locks/queues/atomics with their existing threads don't find this to be too, too disruptive.
- gorjusborg 4y agoasync/await doesn't solve anything with concurrency, working on a single thread does. you can do that as a design choice in languages that aren't exclusively single threaded.
- jayd16 4y agoWhat do you mean by this? Async/await provides a standard way to handle message passing between threads, a cleaner syntax than nested callbacks and a lot of other niceties over raw threading. Async/await is in many single and multi-threaded languages so I'm not clear on what you're saying.
- gpderetta 4y agoIf you are using threads you wouldn't use nested callbacks in the first place. In a proper threaded model you can use exactly the same syntax that you would use with async/await (futures, future combinators, message passing what have you) except you do not have to randomly annotate your code with awaits.
- gorjusborg 4y ago
- sporkland 4y agoEric Brewer (CAP theorem) wrote about it in 2003: https://web.stanford.edu/class/cs240e/papers/threads-hotos-2003.pdf https://web.stanford.edu/class/cs240e/papers/threads-hotos-2...
- dboreham 4y agoBig thank you from me -- so good to see someone pushing back hard against the async insanity.
- pmoleri 4y agoasync/await reveals to the consumer that at some point it will exit the code realm for some I/O task and that the result will be asynchronous, you like it or not, this is what really happens. Threads approach conceals that fact and wait for the return without you noticing. So, one disadvantage I see, is that you'll need to guess where the time consuming tasks will be done and create threads for them. The most perfomant code will be the one written by who knows better the underlying code.
- RandomBK 4y agoThere was an article making the rounds a few years ago [0] that made some rather blunt and pointed arguments _against_ stackful concurrency. I'm not an expert in this field, but it would seem to me that while some of these arguments don't apply in the JVM world, many others do. Does anyone know how Project Loom has gotten around these constraints? [0] http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2018/p1364r0.pdf http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2018/p136...
- gpderetta 4y agoThere were WG21 papers arguing exactly to opposite of that, so eh. One big (and IMHO the only) advantage of the async/await model is that the stack size can be bounded. But if you control the VM like in the Loom case, you can expand and shrink stacks as needed, so it is much less of an issue.
- zozbot234 4y agoFunny thing, we used to call those "userland threads" or "green threads", and they were even a selling point for Java before 1-to-1 mapping with system threads became more popular. How long before we insist again on having special OS-scheduler support for those "virtual" threads?'
- Matthias247 4y ago> This seems so obvious in hindsight as the "right way". I wonder why we went down that whole async/await craze with so many languages? My theory on is that it evolved as a consequence of people trying to solve problems on a level they were familiar and comfortable with. E.g. let's say there were C and C++ application programmers who wanted to scale their applications to handle more clients than a OS supported at that point in time. Their solution was to use event-driven OS APIs, and build abstractions on top of that (libuv, boost) that they found comfortable to use. Then we went one level further, and compiler and programming language experts got aware about the problem, which tried to solve it with language extensions (async/await). I fully confess I'm guilty of this too, by having used/promoted/evolved various async frameworks in the last 10 years. It's a super interesting problem to work on, a bit on the research side, and it feels fulfilling to design some elegant solutions on top of the "i have to async paradigms" problem. But ultimately the compiler magic on top of callbacks on top of event-driven OS APIs workarounds definitely doesn't seem to be best solution for regular application developers, since the abstractions are very leaky and now application developers need to be aware about how all those things work and work together. I really like the Loom solution and look forward to it, since it means the application space doesn't have to deal with colored functions anymore. But ultimately I'm wondering in the meantime whether we should rather try to find an OS level fix for those OS level problems instead of trying to work around them on a higher level. There's probably 100x the amount of code written on async runtimes than for the kernel schedulers and IO subsystems themselves, which feels wrong.
- davidgay 4y ago> I wonder why we went down that whole async/await craze with so many languages? Because it's a useful abstraction for managing N concurrent activities. In the threaded world, it's known as fork-join.
- moomin 4y agoA more interesting question for me is: has this been tried before? In Java? And why did it fail?
- jpgvm 4y agoIt's much easier to implement async/await, especially in languages that don't have as pervasive a runtime as JVM and/or have substantial amounts of native code extensions that can't be managed, i.e Python/JS/Ruby.
- nu11ptr 4y agoYeah I would imagine anything blocking would need special support, which is definitely a downside. Async/await is no different in that regard, but since explicitely marked, you know anything that isn't "awaited" and yet blocks is gonna break things, so might be easier to avoid mishaps in practice.
- fabioz 4y agoWell, https://github.com/python-greenlet/greenlet https://github.com/python-greenlet/greenlet has been available for quite some time in Python (gevent probably being its most used flavor). Note that it it actually predates the async/await approach which was incorporated into the Python language (so, in Python it was implemented as a third-party library -- even async/await had an implementation based on Python 2 using yield and some decorators: https://pypi.org/project/trollius/ https://pypi.org/project/trollius/).
- jpgvm 4y agoYeah I am familiar but using gevent w/monkey-patching is nowhere near the same experience as using Loom. Not to mention that if you patch threading to make it more gevent friendly you can also run into all sorts of fun with locking that wasn't designed for it etc. I really do think Python should have instead adopted gevent as it's async approach instead of asyncio and async/await etc.
- fabioz 4y agoI must say that I definitely don't like the gevent monkey-patching myself -- I prefer to just use the different APIs, although yes, that can end up in a blocking call when there should be none, but that hasn't been a problem I've been bitten by so far -- although it's been a while since I had to use it too ;)
- merb 4y agobecause green threads and async/await have both upsides and downsides. if it would've been as easy as the comment would've described it, they would've shipped it in jdk 8, but they didn't. they ship a preview in jdk 19. rust's rfc explains some of the drawbacks: https://github.com/rust-lang/rfcs/blob/master/text/0230-remove-runtime.md https://github.com/rust-lang/rfcs/blob/master/text/0230-remo... (which basically explains why a preview took until jdk 19) // Edit: also: void handle(Request request, Response response) { var url1 = ... var url2 = ... try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { var future1 = executor.submit(() -> fetchURL(url1)); var future2 = executor.submit(() -> fetchURL(url2)); response.send(future1.get() + future2.get()); } catch (ExecutionException | InterruptedException e) { response.fail(e); } } is not better or worse than: async Task<IActionResult> Handle() { var url1 = ... var url2 = ... try { var requestTask1 = FetchURL(url1); var requestTask2 = FetchURL(url2); return Ok((await requestTask1) + (await requestTask2)); } catch (Exception ex) { return BadRequest(); } } of course one uses "colored" functions the other not, but both have one thing in common: a bad programmer can make serious mistakes with both.
- nu11ptr 4y agoTo be fair, the comment above is about how easy it is for the _user_. Having read docs on go's scheduler design I don't think many would argue it is easy to write such a runtime for the core devs.
- merb 4y agoI added a snippt which also explains the user side, I doubt either has a benefit. both can be misued. I already seen bad go code because of this and I've also seen bad C# code. In fact go code is harder to reason because of this, since you do not know exactly when a function spawns a new goroutine.
- nu11ptr 4y agoI don't think it is a huge difference, but I still prefer the straight threading model. It uses less keywords, looks like regular blocking code, and most importantly, there is exactly "one" world - not a sync vs. async world that have to coexist. I'm speaking generically, not specific to Java (which I have not written in many years). > In fact go code is harder to reason because of this, since you do not know exactly when a function spawns a new goroutine. This is no different in the async/await world, as you never know what function will launch a new "task" (this concept exists in Rust/Python and I assume others). If you don't need a new "thread", both models allow waiting for multiple things simultaneously.
- akvadrako 4y agoThat quote is wrong. Linux can support over a million threads. Of course they can't all be "active" since there isn't more than a few hundred hardware threads, but that's also true of green threads.
- ec109685 4y agoThere's OS level context switching as threads move on and off the CPU that you don't have to pay with the green threads approach.
- chrisseaton 4y ago> Linux can support over a million threads. How many servers have a spare half-terabyte of RAM to do that though? ((512 * 1024 * 1_000_000) / 1024 / 1024 / 1024 = ~500)
- CraigJPerry 4y agoIn large envs, 512gb ram servers were circa 2015. The servers being racked in those envs today can have > 10Tb and it would be unlikely to rack one with less than 2Tb, that would be a specialised node for some specific workload.
- chrisseaton 4y agoIf you have a 2 TB server, which most cloud instances for example are not, that's still 1/4 of your RAM... just for stacks. That can't be an efficient use of RAM.
- CraigJPerry 4y agoCells in hardware RAM are only used to hold actual data (and some are wasted to alignment). I may claim to have reserved 512gb of ram for stacks, but unless I’m actually using the space (in which case there’s no issue) then physical ram is not being consumed.
- pron 4y ago> I wonder why we went down that whole async/await craze with so many languages? We addressed this, albeit very briefly, in the Alternatives section of the JEP: https://openjdk.java.net/jeps/425 https://openjdk.java.net/jeps/425 There are multiple reasons: 1. Languages that don't already have threads have an implicit assumption built into all existing code that program state cannot change concurrently. This is why scheduling points need to be marked explicitly, and why adding threads — whether user-mode or OS — might break existing code in some very tricky ways. That's the case of JavaScript. 2. Some languages target an IR without control over its backend, making an efficient implementation of user-mode threads difficult, if not impossible. async/await requires only changes to the frontend compiler. That's the case of Kotlin, and, perhaps to a lesser extent, Rust. 3. Some languages have technical features that make implementing user-mode threads efficiently more difficult than in others. Pointers into the stack and careful control over memory allocation make this more challenging in languages like C++ and Rust than in Java.
- closeparen 4y agoHow does async/await mitigate #1? Interleaved execution is enough to give you data races; you don't need actual parallelism.
- pron 4y agoYes, but it requires a special call site (transitively, all the way up the stack) that permits the interleaving, and so cannot sneak into existing code that might implicitly assume no interleaving.
- kodablah 4y ago> I wonder why we went down that whole async/await craze with so many languages? For single-threaded event loops because most of those languages did not want to put the concept of thread safety onto the developer (e.g. JS and Python). And even the ones that do concern the user w/ thread safety don't have an intermediate representation and VM to automatically sequence instructions (e.g. Rust).
- stonemetal12 4y ago>Every unit of concurrency in the application domain can be represented by its own thread, making programming concurrent applications easier. Funny, I have never heard anyone describe threads as the thing that made concurrent programming easier, or more threads would make it easier. What makes concurrent programming easier is thread interaction models that make it hard to lock the process. async\await, give you concurrency without the need to for the developer to explicitly write lock code. Much easier than having to manually handle that interaction.
- cogman10 4y ago> async\await, give you concurrency without the need to for the developer to explicitly write lock code. async\await in no way relieve the developer from needing to worry about locks. If you have shared mutable memory, you have locks. If you aren't considering that, then you've got broken code. > What makes concurrent programming easier is thread interaction models that make it hard to lock the process. Yes, the way you do that is by focusing on message passing and immutable data when you can get away with it and, ideally, prebuilt thread safe datastructures when you can't. What async\await buys is lightweight threading when lightweight threading isn't available. It allows you to have millions of concurrent processes running at the same time. HOWEVER, the cost of this is the "colored function" problem. You HAVE to mark up your code to let the compiler/framework know "this is code that can block and thus needs to be able to give up control and resume". This problem is difficult because you can't simply call an async function from a non-async function. You have to do some wrapping/juggling to make everything play nice. The reason lightweight threading is nice is because you no longer have the colored function problem. From any reference or context you can say `CompletableFuture.supplyAsync(()->calculateValue())` and have a new concurrent action spawned. If that action does IO or whatever, it doesn't hog a thread, it just yields (like async/await does) and lets another task move forward. The only reason for async/await is OS threads are expensive and have pretty large negative implications on operating systems.
- SemanticStrengh 4y agoJS async is lock free though.
- noncoml 4y ago> This seems so obvious in hindsight as the "right way". I wonder why we went down that whole async/await craze with so many languages? Hindsight? There are many of us that have been advocating against the async/await madness. I said it before and I will say it again. Async is today what OOP used to be in the nineties.
- kjeetgill 4y agoOOP isn't that bad in moderation! My personal proprietary blend looks like 5%/20%/35%/40% declarative/functional/OOP/imperative these days. I'm definitely wary of letting too much functional or OOP zealotry. I try to avoid all the monad/applicative/functor stuff and too many abstract base classes, excessive interface indirection. But I am a Java pleb.
- zmmmmm 4y agoOOP is bad in the way that many magnificently successful things are bad. People only work up the energy to fully critique things when they are so pervasive and ubiquitous. At which point they are entirely associated with their flaws and none of their positive attributes which are all treated as orthodoxy.
- olliej 4y ago> This seems so obvious in hindsight as the "right way". I wonder why we went down that whole async/await craze with so many languages? That’s basically what async swift does? It is also orthogonal to async/await. Async/await is a mechanism a programming language uses to allow you to write straight line code without having to write blocking code. You could resolve that by creating a hulking thread, but that just means you need to await a thread result :) Also JS isn’t going anywhere, and it seems unlikely it will ever get multiple execution threads