Live data from Hacker News

Project Loom and Structured Concurrency

javaadvent.com

31–40 of 111 posts

Re: Project Loom and Structured Concurrency

#31

I have a lot of experience using concurrency in Go, and for the last couple years have been at the bleeding edge of Python async. The tradeoffs between the two approaches are immense. With the virtual thread model you have: * No function coloring problem. This also means existing code is easier to port. * possibility of transparent M:N scheduling. * Impedence mismatch with OS primitives. * Much more sophisticated run…

* Java offers both user-mode and kernel threads. You pick at creation, and can even plug your own scheduler.

* Loom's virtual threads are completely scheduled in library code, written in Java.

* FFI that bypasses the JDK and interacts with native code that does either IO or OS-thread synchronization is extremely rare in Java.

* Cancellation is the same for both.

Also, IMO, coordination is simpler for threads than for async. Where they differ is in their choice of defaults: thread allow scheduling points anywhere except where explicitly excluded; async/await allows scheduling points nowhere except where explicitly allowed. Putting aside that some languages have both, resulting in few if any guarantees, threads' defaults are better for correct concurrency. The reason is that correctness relies on atomicity, or lack of scheduling points in critical sections. When you explicitly exclude them, none of your callees can break your correctness. When you explicitly allow them, any callee can become async and break its caller's logic. True, the type system will show you where the relevant callsites are, but it will not show you whether there is a reliance on atomicity or not.

Async/await does, however, make sense for JavaScript, where all existing code already has an implicit assumption of atomicity, so breaking it would have broken the world. For languages that have both, async/await mostly adds a lot of complexity, although sometimes it is needed for implementation reasons.

Re: Project Loom and Structured Concurrency

#32
post #19

Since the article goes out of its way to not mention Kotlin, I'll do it for them since this is both lame and more than a bit disingenuous. Arguably, Kotlin co-routines (and the Flow API) provides a very nice implementation of the exact same concepts on the JVM. As far as I know, the loom integration is already planned and probably implemented to a large degree. Mostly doing that should be straightforward as this pret…

It's like saying the article goes out of its way not to mention Scala/Haskell's IO type. Syntactic coroutines, monadic IO, and threads are different constructs, although they are different ways to address a similar problem -- expressing sequential (and, in contrast, parallel) composition. Virtual threads are Java threads; there's nothing "bolted". Syntactic coroutines are a kind of syntactic code-unit similar to subr…

It's more than that. Java developers are switching to Kotlin; notably on Android and Spring code bases (well over 50% of backend Java projects at this point apparently).

This is an article about structured concurrency and how Loom implements something that Kotlin has shipped that both Android and Spring heavily integrate with already.

So, there's more than a casual relation between Kotlin co-routines and Loom. I'd go as far that they apparently took a really good look at it and somehow ended up with something that essentially implements almost 1 to 1 the same kind of things.

Roman Elizarov (tech lead for co-routines, and recently the whole language) has been talking about structured concurrency a lot. I don't think he invented the notion but it definitely is what co-routines is designed to do.

So, when I see an article talking about how great Loom and structured concurrency is mentioning several languages but yet somehow glossing over Kotlin, I call it out as lame. Oracle no doubt has good reasons to not want to talk about Kotlin. But to me it is clear they consider it a threat. It's not the first time that they celebrate a few features new to Java where they mention other languages as influence and yet not mention Kotlin. Recent introduction of Records is a good example.

The Threading APIs date back to the late nineties and are full of complex stuff that you need to be aware of if you go near them if you care about avoiding all sorts of interesting categories of bugs, issues, and pitfalls. So much that I'd treat the occurrence of import java.lang.Thread as a giant red flag in a code review. Typically, it's a rookie mistake to use that. You shouldn't have to; there are better APIs.

Kotlin, btw. treats threads just as a special case of co-routines. You can have co-routine dispatchers that represent a particular executor. This is how you separate slow blocking IO things from e.g. CPU heavy stuff, or UI event handling. So, there is no API split in Kotlin. Some co-routines use a threadpool, some don't.

Re: Project Loom and Structured Concurrency

#33
post #19

Earlier quoted context omitted.

It's like saying the article goes out of its way not to mention Scala/Haskell's IO type. Syntactic coroutines, monadic IO, and threads are different constructs, although they are different ways to address a similar problem -- expressing sequential (and, in contrast, parallel) composition. Virtual threads are Java threads; there's nothing "bolted". Syntactic coroutines are a kind of syntactic code-unit similar to subr…

It's more than that. Java developers are switching to Kotlin; notably on Android and Spring code bases (well over 50% of backend Java projects at this point apparently). This is an article about structured concurrency and how Loom implements something that Kotlin has shipped that both Android and Spring heavily integrate with already. So, there's more than a casual relation between Kotlin co-routines and Loom. I'd go…

> So, there's more than a casual relation between Kotlin co-routines and Loom.

Not beyond the fact it was one of the things we looked at and decided to go in a completely different direction. We looked at Python, Go, JavaScript, C#, Kotlin, C++, Rust, Zig, Haskell, Pony, Scheme, Erlang, Céu, Scala and Clojure, and decided not to go down the C#/Kotlin path, which is why we ended up with a solution that is nothing alike. The positive influences were Erlang, Go, and Scheme (with a glance to Céu). That's why virtual threads share a lot of similarities with Erlang processes and Go goroutines, and borrow ideas from Scheme's (and OCaml's) multi-prompt delimited continuations, but are not at all like C#/Kotlin's syntactic coroutines.

There are certainly things about Kotlin we love, like nullability types, and that we'd like to see in the Java language some day. When we do, Kotlin would have been the influence. Syntactic coroutines, on the other hand, were something to avoid.

> Java developers are switching to Kotlin

Just as they had to Scala in the past, many developers are switching to Kotlin, which has reached ~2-4% on the Java Platform, and might even reach 5-7% some day. That is huge, perhaps unprecedented, market penetration for a Java Platform language, but let's not get carried away.

There are certainly languages we consider serious competitors, but Kotlin is still an order of magnitude in size away from being one of them. Java is so big that you can still be 20-50x smaller and still be a very popular language, yet not quite a competitor.

> Recent introduction of Records is a good example.

Nope. First of all, Kotlin doesn't have records. Kotlin-like data classes were something we looked at (we look at all languages) and said, we don't want that, we want records. Second, the inspiration for records was ML. So records are actually yet another example where we decided not to go in the same direction as Kotlin. This isn't to say Kotlin did something worse or better, but it did do something decidedly different.

> Typically, it's a rookie mistake to use that. You shouldn't have to; there are better APIs.

The same APIs Java users use today (Executors) are the ones they'll use when Loom lands. There is still no need to use the Thread API directly.

> Kotlin, btw. treats threads just as a special case of co-routines.

Perhaps in your desire to see Kotlin influences everywhere (and, BTW, syntactic stackless coroutines were done in C# first, at least among the well-known language) you misunderstand what Loom is. Virtual threads are threads, period.

Anyway, it's perfectly fine to prefer Kotlin over Java (the language) just as it is to have the opposite preference. But I think you misjudge the influence those languages have and draw from.

Re: Project Loom and Structured Concurrency

#34

I have a lot of experience using concurrency in Go, and for the last couple years have been at the bleeding edge of Python async. The tradeoffs between the two approaches are immense. With the virtual thread model you have: * No function coloring problem. This also means existing code is easier to port. * possibility of transparent M:N scheduling. * Impedence mismatch with OS primitives. * Much more sophisticated run…

I don't think "task cancellation" is quite the major difference you think. If you model it as thread A wants to cancel thread B, then while threading means that A runs and cancels B, but B may need some time to catch up, the async world has the problem of thread A running at all to cancel B, if B is having a problem that requires cancellation. It's "obvious" and "safe" until it doesn't happen at all.

This is a pervasive problem with the async/await model. As it scales up the probability of something failing to yield when it should and blocking everything else continually goes up as the code size increases, and then the whole model, correctness, practicality, and all, just goes out the window. While it is small for small programs, and it the scaling factor often isn't that large, it is still a thing that happens. Entire OSes used to work that way, with the OS and user processes cooperatively yielding, and what killed that model is this problem.

Also, I'm writing a lot of code lately where I can peg multiple cores at a time, with a relatively (if not maximally efficient) language like Go; having to also write it as a whole bunch of OS processes separately running because my runtime can only run on one core at a time is a non-starter, and "async/await" basically turns into a threading system if you try to run it on multiple cores in one system anyhow.

These two fatal-for-me flaws mean it's a non-starter for a lot of the work I'm doing anyhow, regardless of any other putative advantages.

(As I mentioned, I'm using Go, but if you want to see a runtime that really has the asynchronous exceptions thing figured out, go look at Erlang. Having a thread run off into never-never-land and eating a full CPU isn't fun, but being able to log in to your running system, figure out which it is using a REPL, kill just that thread, and reload its code before restarting it to fix the problem, all without taking down the rest of your system is not an experience most of you have had. But it can be done!)

Re: Project Loom and Structured Concurrency

#35
post #31

I have a lot of experience using concurrency in Go, and for the last couple years have been at the bleeding edge of Python async. The tradeoffs between the two approaches are immense. With the virtual thread model you have: * No function coloring problem. This also means existing code is easier to port. * possibility of transparent M:N scheduling. * Impedence mismatch with OS primitives. * Much more sophisticated run…

* Java offers both user-mode and kernel threads. You pick at creation, and can even plug your own scheduler. * Loom's virtual threads are completely scheduled in library code, written in Java. * FFI that bypasses the JDK and interacts with native code that does either IO or OS-thread synchronization is extremely rare in Java. * Cancellation is the same for both. Also, IMO, coordination is simpler for threads than for…

Is it possible to override the scheduler from user code? i.e. if you wanted to control scheduling yourself for some reason.

Re: Project Loom and Structured Concurrency

#36

Since the article goes out of its way to not mention Kotlin, I'll do it for them since this is both lame and more than a bit disingenuous. Arguably, Kotlin co-routines (and the Flow API) provides a very nice implementation of the exact same concepts on the JVM. As far as I know, the loom integration is already planned and probably implemented to a large degree. Mostly doing that should be straightforward as this pret…

> Since the article goes out of its way to not mention Kotlin, "Since the article goes out of its way to not mention [Rust|Kotlin]" and "I'm surprised this article has no mention of [Rust|Kotlin]" must be one of the most frequently used templates on HN.

The difference is that Kotlin has been gobbling up Java users using Java frameworks like Android and Spring. Coroutines are used in both and implement structured concurrency exactly like Loom does. It's not a little bit similarish and vaguely related: it maps 1 to 1 conceptually; well at least for the stuff that Loom actually implements.

Rust indeed would deserve a mention as well but it follows a somewhat different approach to the same problem. But Kotlin is somewhat special here as Oracle is bleeding users to specifically Kotlin.

Re: Project Loom and Structured Concurrency

#37
post #31

Earlier quoted context omitted.

* Java offers both user-mode and kernel threads. You pick at creation, and can even plug your own scheduler. * Loom's virtual threads are completely scheduled in library code, written in Java. * FFI that bypasses the JDK and interacts with native code that does either IO or OS-thread synchronization is extremely rare in Java. * Cancellation is the same for both. Also, IMO, coordination is simpler for threads than for…

Is it possible to override the scheduler from user code? i.e. if you wanted to control scheduling yourself for some reason.

Yes, and even on a per-thread basis.

Re: Project Loom and Structured Concurrency

#38
post #37

Earlier quoted context omitted.

Is it possible to override the scheduler from user code? i.e. if you wanted to control scheduling yourself for some reason.

Yes, and even on a per-thread basis.

Thanks! Is there an example "Hello World" executor that demonstrates the minimum functionality needed?

I would like an executor that runs all tasks on a single thread and schedules them deterministically for testing.

Re: Project Loom and Structured Concurrency

#39
post #34

I have a lot of experience using concurrency in Go, and for the last couple years have been at the bleeding edge of Python async. The tradeoffs between the two approaches are immense. With the virtual thread model you have: * No function coloring problem. This also means existing code is easier to port. * possibility of transparent M:N scheduling. * Impedence mismatch with OS primitives. * Much more sophisticated run…

I don't think "task cancellation" is quite the major difference you think. If you model it as thread A wants to cancel thread B, then while threading means that A runs and cancels B, but B may need some time to catch up, the async world has the problem of thread A running at all to cancel B, if B is having a problem that requires cancellation. It's "obvious" and "safe" until it doesn't happen at all. This is a pervas…

Async, and cooperative multitasking in general, requires all members participate in the contract: you shall not block and you shall not go too long until yielding. Once a piece of code violates that, all bets are off. Python has explicit debugging mechanisms to help a developer detect the latter.

Cancel safety is less about killing an out-of-control task, and more about making sure the state after cancelling a task is consistent.

Re: Project Loom and Structured Concurrency

#40

Since the article goes out of its way to not mention Kotlin, I'll do it for them since this is both lame and more than a bit disingenuous. Arguably, Kotlin co-routines (and the Flow API) provides a very nice implementation of the exact same concepts on the JVM. As far as I know, the loom integration is already planned and probably implemented to a large degree. Mostly doing that should be straightforward as this pret…

You're getting downvoted because of your snarky opening statement.

But I do think it's important/relevant to compare virtual threads to Kotlin coroutines.

I agree with your point that tacking all of this onto the existing (flawed) Thread API is a risky move. I understand the reasoning on both sides, but I'm not usually a huge "backwards compatible at all costs" or "don't make people learn new things" proponent on anything. So that's my bias.

I think you're painting the `suspend` keyword a bit rosy, though. The fact that Kotlin has colored functions is a huge pain in the ass. You have to design different APIs sometimes to account for a "suspend version" and a "non-suspend version".

The idea with Loom (like goroutines, which is the first green thread model I've used) is that async stuff is so cheap that you can almost pretend it doesn't even matter if something calls a coroutine. I'm not sure if that's the best solution, though. One advantage that colored functions do have is that you see it and "know" that the thing involves expensive and/or blocking work. With coroutines, how do you know if calling a function will slow your current thread down as it waits for the results? That's a question we could ask Go devs today, I suppose.

I agree with your prediction that Kotlin's coroutines might just sit on top of virtual threads on the JVM in the future.

Post reply on HN