8 ms·
This is my favorite JVM project and I think it's going to be huge! This model of concurrency is so much better than the async/await model used by many other la
by ceronman 6y ago
This is my favorite JVM project and I think it's going to be huge!
This model of concurrency is so much better than the async/await model used by many other languages. No more colored functions [1], or worse Completable<Future>, promises et al. Nice stacktraces, debuggers that work plus no need for thread pools anymore. I can't wait for this to be ready for production.
Only drawback seems to be when calling native code. I guess it's the same problem that Golang has. Good thing is that the Java ecosystem is not that dependent on native stuff, so I think it's a fair tradeoff to make.
[1] https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
- anonymoushn 6y agoWill virtual threads be copyable?
- mrkeen 6y ago> No more colored functions [1] That's a property of the function, whether or not you want the type system to help you with it. I can call a function myRedFunction, but if it calls a blue function, it's blue. > or worse Completable<Future> What is this? Future<Result> f = e.submit(() -> { ... return result; }); // spawns a new virtual thread > This model of concurrency is so much better than the async/await mode ThreadFactory tf = Thread.builder().virtual().factory(); ExecutorService e = Executors.newUnboundedExecutor(tf); Future<Result> f = e.submit(() -> { ... return result; }); // spawns a new virtual thread ... Result y = f.get(); // joins the virtual thread I don't get it. This just looks like async/await (submit/get) with extra steps. Why not just write Future<Result> f = async ( ... result; ) Result y = await (f);
- pron 6y ago> That's a property of the function, whether or not you want the type system to help you with it. The problem is that in, say, C# or Kotlin, subroutines with the same semantics come in two different colours. The type system "helps" you distinguish between things that aren't meaningfully distinguishable. > I don't get it. This just looks like async/await (submit/get) with extra steps. You only need to submit if you want to do stuff in parallel and then join (also, there are no extra steps). The analogue to: var a = await doA(); var b = await doB(); is just: var a = doA(); var b = doB();
- h-cobordism 6y ago(1) That sounds like a recipe to unintentionally miss a ton of concurrency. Having to put an `await` there is a great indicator that you're forcing an order of execution. (2) Can you give an example of an async and a non-async subroutine that have "the same semantics"?
- pron 6y ago1. There is no need for await. Threads imply sequential execution. 2. sleep vs. delay in either C# or Kotlin.
- nlawalker 6y ago> 1. There is no need for await. Threads imply sequential execution. How do you execute doA() and doB() concurrently? In C# you can do something like: var t1 = doA(); var t2 = doB(); await Task.WhenAll(new[] {t1, t2}); var a = t1.Result; var b = t2.Result; EDIT: Ah, never mind, I just saw "You only need to submit if you want to do stuff in parallel and then join" above.
- jerf 6y agoThe reality is that you rarely want to doA and doB concurrently, so optimizing syntax for that case is not useful, whereas you want to be able to call functions without having to worry about their color all the time, where "all the time" here is typically >1 time per function. Many of you are perhaps scratching your head and going "What? But of course I concurrently do multiple things all the time!" But this is one of those cases where you grossly overestimate the frequency of exceptions precisely because they are exceptions, and so they stick out in your mind [1]. If you go check your code, I guarantee that either A: you are working in a rare and very stereotypical case not common to most code or B: you have huge swathes of promise code that just chains a whole bunch of "then"s together, or you await virtually every promise immediately, or whatever the equivalent is in your particular environment. You most assuredly are not doing something fancy with promises more than one time per function on average. This connects with academic work that has showed that in real code, there is typically much less "implicit parallelism" in our programs than people intuitively think. (Including me, even after reading such work.) Even if you write a system to automatically go into your code and systematically finds all the places you accidentally specified "doA" and "doB" as sequential when they could have been parallel, it turns out you don't actually get much. [1]: I have found this is a common issue in a lot of programmer architecture astronaut work; optimizing not for the truly most common case, but for the case that sticks out most in your mind, which is often very much not the most common case at all, because the common case rapidly ceases to be memorable. I've done my fair share of pet projects like that.
- Skinney 6y agoWell, the first two lines are usually written somewhere at the top level. You won't actually write that anytime you want something to be async. The last two lines are what you usually write, so most of the time, the number of lines of code are equivalent. Second, Java doesn't have async/await today. If Java was to introduce it, it wouldn't be compatible with the code written today. The big benefit of project loom is that code written today will get all the benefits of async/await without changing any code. In fact, because it's not important to pool threads, it actually gets easier. The big problem with async/await in languages like C# or even coroutines in Kotlin is that it doesn't mix with the "old" api. The get the benefits you need to do a huge refactor of your code, and you need to make sure that any library you pull in is compatible. Java's approach seems much better.
- jayd16 6y agoYou don't. You can call synchronous functions from async functions just fine and vice versa. The syntax isn't identical but I don't understand this split the world meme.
- toast0 6y agoIn my experience with C#, I ran into apis I wanted to call that were async. I couldn't call them, even as Result foo = await async_thing(), unless my function was marked as async. I don't remember having a problem calling sync functions from async functions though.
- uryga 6y agoafaik sync can call async if the language allows you to make an executor/event-loop. e.g. with python's asyncio it'd be something like def sync_foo(): task = async_bar() loop = asyncio.new_event_loop() return loop.run_until_complete(task) after all, unless the loop is built into the language (like JS), you need bootstrap the async code somehow
- jayd16 6y agoYou can just use: task.RunSynchronously() mildly annoying but far from impossibly split.
- eggsnbacon1 6y ago>I don't get it. This just looks like async/await (submit/get) with extra steps. Why not just write because altering the underlying thread pool can convert all existing code to fibers without syntax changes. This is the crux of "colored functions", two different signatures for async/non-async. The implications for improving the performance of existing code are enormous. Most Java API's give you at least indirect control of the thread pool used. Which means they can be converted to fibers without updating the library. In Java, executors are basically thread pools. If you can update the executor to use fibers the rest of the code is using fibers now. Your second example only converts a single call site to fibers. A thread pool could be used in hundreds of places. In practice, this is a huge difference. I can update my Streams to use a fiber pool, and all across my app the hundreds/thousands of streams are using fibers now. Same story with REST and DB access. There's usually a single thread pool for each. A few lines of code to update the pools and my REST and DB calls are all using fibers.
- balfirevic 6y agoI still have't fully made peace with the fact that C# choose async/await route :( It could have been so much better.
- nlawalker 6y agoWhy didn’t C# or JS go this way?
- balfirevic 6y agoFor JS, cynical take would be that Javascript is just being consistent in making every wrong choice in language design. More serious (and more correct) answer is that, being single-threaded, adding any kind of threads would be pretty adventurous. Async/await as a sugar for promises is a decent improvement over using promises directly, so it's not so bad of a choice after all. As for C#, some kind of fibers appear to have been considered but deemed too hard or not worth the trade-offs. See here: https://github.com/dotnet/runtime/issues/11084 https://github.com/dotnet/runtime/issues/11084 The acceptable alternative was, apparently, forcing to programmers to litter their code with words such as async/await/task in-between the words and symbols that actually describe the purpose of their program, should they want to take advantage of non-blocking IO. All the way throughout the call-stack, nonetheless. And you also get crap like this: https://github.com/dotnet/corefx/pull/4868 https://github.com/dotnet/corefx/pull/4868
- int_19h 6y agoWin32 has fibers, which are essentially green threads. And for a while, .NET itself tried to support it. The problem is that there was never widespread support on the native side of things, and in .NET itself, user code has to not make certain assumptions to allow for fibers to work (e.g. that each managed thread maps 1:1 to an OS thread), which in practice nobody followed. This points at the bigger problem that every implementation of green threads has: it breaks interop unless everybody buys into it. Async/await is fundamentally callback-based, which can be represented nicely even in C ABI - so literally any language that can interop with C, can interop with an async/await model, with execution flow asynchronous throughout the entire chain. Runtimes that choose to roll their own green threads, like Go, and I guess now also Java, are effectively stating that their convenience is worth the inability to interface well with async code outside of their runtime.
- albertzeyer 6y agoCan you maybe elaborate why this model is better than async/await? I have to admit, I don't really know Loom, but from a first glance, it looks like this is just (green) threads, and async/await is anyway an orthogonal concept to this, or not? But maybe I am confusing things here. Can you maybe recommend a good overview over all the current concurrent approaches and concepts, like async/await, and alternatives, and their advantages and problems? I would love to get a more recent overview on this. I had the impression that most recent languages follow on the async/await concept. At least JS and Python. Maybe also Rust? Go? Erlang? And there are probably libraries for C++ to do the same, or maybe it is already integrated? I have to admit that I did not fully follow all this development.
- Skinney 6y agoProject Loom provides the same benefit of async/await, but without changing the syntax in any way. In C# you now have a split API: blocking api's which rely on threads for concurrency, and async api's which require you to use async/await syntax. These api's are not compatible, so to take advantage of async/await, you have to refactor your code. When project loom is implemented in Java, all code which makes use of threads today automatically gains all the benefits of the async/await model, without any refactoring. Go and Erlang doesn't do the async/await thing. They only have one notion of concurrency (goroutines in Go, processes in Erlang). They don't require you to use async and await constructs, but they work similarly to async/await under the hood.
- GolDDranks 6y agoAsync/await split up your functions at their "blocking points" to fragments that can be run fragment-by-fragment, in an interleaved way by an executor / event loop, thus enabling high level of concurrency within a single OS thread. The problem is that your split-up async function ceases to be a normal function. It doesn't use the stack in a similar way than normal functions. Furthermore, the executor / event loop is required for running those async functions. So you can't call async functions from normal functions, because they aren't. You need to create an event loop and then hand the task over for it to process it. Your world splits up into sync and async functions. Go and Erlang are going with the similar "lightweight thread" approach as Java's project Loom. On the other hand, JS, Python and C# have gone with async. I'm not sure if these languages actually need async, going with something similar as project Loom would have also fit, and would have been simpler from the user perspective, perhaps? But hindsight is 20/20. Rust has also gone async, and I'd argue it's the only one of the pack who really have async/await as a necessity - this is because of two design requirements that differ from Java, C#, Python and JS: 1) must have native-level performance when calling foreign code that expects a C-like stack. Something like Loom doesn't provide that. 2) must not have an implicit default runtime.