Live data from Hacker News

Project Loom and Structured Concurrency

javaadvent.com

81–90 of 111 posts

Re: Project Loom and Structured Concurrency

#81
post #7

> It does nothing for you if you have computationally intensive tasks and want to keep all processor cores busy. I would argue this isn't concurrency at all (the job of juggling mostly independent tasks, and scheduling them to a relatively small number of processing units), but parallelism (the job of performing a single computational task faster by employing multiple processing units), and exactly the job of paralle…

>All threads created by this factory will be virtual threads that are always "carried" by the UI OS thread

Does this actually solve the problem? I don't see it. We want to interweave foreground and background work. Sometimes that means blocking work will yield, sometimes that means it should not yield because conceptually several tasks should retain exclusive control of that thread. You might want some IO task on the background but you need a block of OpenGL tasks to retain control.

I just don't see how you can do this implicitly in a way that's cleaner than async/await. It seems like posting tasks to this thread factory or that will get the job done but is that an improvement?

It sounds like for now this stuff will still be using the current model of posting unyielding runnables to a thread. That's fine I guess. Loom still seems very cool, it just doesn't cover the cases I deal with a lot more often.

Re: Project Loom and Structured Concurrency

#82
post #59
post #57

Earlier quoted context omitted.

Fair point about the abstraction level of a Java thread vs an OS thread. In light of both things you just wrote, let me ask you this: why does Java give us any choice on thread implementation? Why not have everything be a green thread from now on? If Java threads are not OS threads, can be paused for any amount of time, and there's no reason that network calls or db calls or file IO should be treated any differently,…

The first part of the answer is the same as that for a similar question we've been asked about LinkedList: we don't deprecate things that are heavily used, however useless or superseded by something else, unless they are very harmful. Deprecation in Java does not mean "unrecommended" but "absolutely do not use this if you want your program to continue working on future versions." Not only is it a compile-time warning…

> The second part of the answer is that there are still good uses cases for heavyweight threads, that are backed by one OS thread.

Hold the phone. Didn't you just say a few comments up that I shouldn't think of a "Java thread" like an "OS thread"?

If I use a Thread, today, how do I know if it's corresponding to an OS thread? I understand that it will, theoretically, depend on the platform on which the JVM is running. But on a non-exotic platform (x86/ARM Windows, Linux, macOs, Android, etc), does one Java Thread map to one OS Thread or not?

Re: Project Loom and Structured Concurrency

#84

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…

As I see it, the main difference is that existing code will magically work with Loom and will require rewrite with coroutines. I can get Oracle 9i JDBC driver from 1999 and use it under Loom and it probably will work just fine. Probably Oracle will not rewrite its JDBC drivers with Kotlin coroutines any time soon.

Well, Spring's r2dbc (reactive db abstraction) works great with co-routines on top of Spring's reactor framework. The integration for that is well supported by Spring. Drivers that support asynchronous IO, at this point include most obvious mainstream databases. I'd expect that stuff will work with Loom as well. Though we may have to wait a while for Spring to release support for that.

Oracle has not gotten around to supporting Oracle DB with r2dbc just yet apparently: https://stackoverflow.com/questions/58813658/r2dbc-oracle-da... but it appears to be in the works. Given that they are developing Loom, they probably aim to have that stuff working together perfectly with their own DB. If/when it does, it will work with kotlin co-routines as well. Basically, if it runs with Java, you can use it from Kotlin and trivially wrap it with a co-routine. I've done this recently for several other database that support async IO with some callback mechanism.

That old Java driver will also work fine with Kotlin & co-routines as well However, you probably want to be using the IO Dispatcher to ensure you have enough threads to deal with it blocking your process thread. So your server doesn't hang. That kind of is what structured concurrency is about. Of course, most blocking IO database drivers would typically use some connection pool backed by a (real) thread pool. Not sure how Loom 'magically' deals with interrupting IO blocked virtual threads but I have hunch that just means everything on the underlying OS thread ends up being blocked. Using virtual threads in a connection pool is probably going to end in tears. I'm not aware of any magic that Loom provides that addresses that other than just allowing you to use either OS threads (real?) or virtual threads (aka. fibers, co-routines, green threads, light weight threads, etc.).

Re: Project Loom and Structured Concurrency

#85
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…

> Scala/Haskell's IO type

Since you've brought out Haskell's IO and Scala libraries' IOs (Monix, ZIO, Cats Effect), or F#'s Async, I think it's worthwhile to point out how they're different from the async-await approach that's in C#/Kotlin/Rust/etc.

They both require "special" handling -- in C# it's the `async`/`await` syntax, in Scala it's the flatMap function or for-comprehension -- there they are similar. But their meaning is different. IO/Task in Scala doesn't represent a possibly started and under-way computation; it represents a "dead" program, yet to be started, a mere value. And it has all the advantages that mere values have, like refactoring (extract variable, ...) or restarting in case or failure and so on. Pass it into a method, return it from a method, store it in a data structure/collection, create `IO[IO[X]]`, whatever you want, just like you would with `Option[X]` or `List[X]`. In Scala, you have to differentiate between `A => B` and `A => IO[B]`, because `B` and `IO[B]` are different, but both are still values. Your program then ends up being this one big IO/Task value, which is then executed "at the end of the world". These pictures illustrate it quite well:

https://twitter.com/impurepics/status/1182946618280153094

https://twitter.com/impurepics/status/1180064851219144704

On the other hand, async-await has none of the benefits, only downsides. You get functions of two colors, but no benefit in return. It's justifiable in Rust, because Rust aims for zero-cost abstractions. But for Kotlin/C#, it's a sad choice.

The Loom approach for Java is a reasonable one. No async-await shenanigans, no funny FP/Haskell/IO business. You just use threads for concurrency as God intended them and you can have gazillions of them, because they are M:N. And I respect that, even though I'm partial to IO/Task for the reasons outlined above.

Re: Project Loom and Structured Concurrency

#86
post #49

Earlier quoted context omitted.

What makes you think that Kotlin should be mentioned? Especially since concurrency in Kotlin is really not great, compared to concurrency in Erlang or Scala.

I'm not familiar with concurrency in Erlang or Scala, could you explain how it differs from the approach in Kotlin (which I know)?

I've posted above how Scala's IO/Task is different from C#/Kotlin async-await.

https://news.ycombinator.com/item?id=25305574

I hope you will find it helpful.

Re: Project Loom and Structured Concurrency

#87

Programming challenge in golang: Create a persistent tcp client that can connect to a server, read responses… and be disconnected via context.WithCancel().

I'm not familiar enough with Go to understand why this is a challenge. What point are you trying to illustrate with the challenge? Is this easy or hard, and how does this compare to Loom and Structured Concurrency?

Re: Project Loom and Structured Concurrency

#88
post #82
post #59

Earlier quoted context omitted.

The first part of the answer is the same as that for a similar question we've been asked about LinkedList: we don't deprecate things that are heavily used, however useless or superseded by something else, unless they are very harmful. Deprecation in Java does not mean "unrecommended" but "absolutely do not use this if you want your program to continue working on future versions." Not only is it a compile-time warning…

> The second part of the answer is that there are still good uses cases for heavyweight threads, that are backed by one OS thread. Hold the phone. Didn't you just say a few comments up that I shouldn't think of a "Java thread" like an "OS thread"? If I use a Thread, today, how do I know if it's corresponding to an OS thread? I understand that it will, theoretically, depend on the platform on which the JVM is running.…

I meant that a Java thread, like the List interface, is an abstraction with multiple implementations for you to choose from.

In OpenJDK (i.e. the Oracle implementation of Java) today, a Java thread is implemented as a wrapper of an OS thread; I believe OpenJ9, the IBM implementation, does the same. But the Java specification does not require it. Loom might change the specification to require it, or leave the implementation of "platform threads" (i.e. non-virtual threads) up to the specific Java implementation.

Re: Project Loom and Structured Concurrency

#89
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…

> Scala/Haskell's IO type Since you've brought out Haskell's IO and Scala libraries' IOs (Monix, ZIO, Cats Effect), or F#'s Async, I think it's worthwhile to point out how they're different from the async-await approach that's in C#/Kotlin/Rust/etc. They both require "special" handling -- in C# it's the `async`/`await` syntax, in Scala it's the flatMap function or for-comprehension -- there they are similar. But thei…

> Loom approach for Java is a reasonable one. No async-await shenanigans, no funny FP/Haskell/IO business.

There's another important piece of the puzzle (putting aside any debate over the value of values): software is much more than syntax. When I think of some software construct, I don't just think how I can express and manipulate it in code, but also how I can express it and manipulate it while it is running and after it's run, i.e. present it in a profiler or assist troubleshooting when something goes wrong. In other words, how to make it a traceable, contextual entity.

The "process" construct on the Java platform -- regardless of the frontend language used to write the program -- is not that of an IO type, nor is it a syntactic coroutine; it is the thread. The JVM constructs stack traces for threads; it emits profiling, monitoring and debugging events for threads; its semantics of single-stepping follow threads; its GC heuristics are or can be designed around threads and even the JIT compilers perform optimisations that are implicitly based on threads [1]. In other words, the syntactic construct is just a part of of the problem, and the goal was not just to find a good fit for the language, but also all of the other important aspects of software. Adopting any other alternative would have required introducing that new concept into all layers of the platform as well. We're building a platform, not just a language.

Just to give you an example of a design issue we're struggling with now because even the mere number and duration of threads changes some of their runtime aspects: how do we perform thread dumps in a way that would tell the user what they want to know, i.e. what the different parts of their application are currently doing? Merely dumping a million stack traces probably wouldn't do the job, even if we grouped and deduped them. If we were to introduce a new kind of process, such "program dumps" would be the least of our worries; just the coordination with debugger/profilers/APM vendors would be a multi-year project.

> https://twitter.com/impurepics/status/1180064851219144704

OT, but, as someone who's interested in analytic philosophy and formal languages, such incorrect usages of "referential transparency" are a pet-peeve of mine. Java is more referentially transparent than Scala (at least Scala 2.x) because it doesn't have macros. It does not mean "an expression can be replaced by its value without changing [the value of the value of an enclosing expression]" but "an expression (term) can be replaced by its reference (aka denotation) without changing the reference of its enclosing expression;" hence referential transparency -- a syntactic term is a transparent representation of its reference, something that isn't true once you have macros. It's just that if your language is a pure-functional one, i.e. one with value semantics, i.e. one whose term references are values in the object domain of the language, then a reference is a value. That incorrect usage of referential transparency is nothing but a synonym for being pure functional (or of "value semantics") rather than a feature of it.

> It's justifiable in Rust, because Rust aims for zero-cost abstractions.

It's not that async/await is inherently more "zero-cost" than user-mode threads; they're just like that in Rust given its peculiarities. A different syntactic construct allows them to restrict recursion and virtual dispatch that would make stack size non-deterministic and interfere with their chosen memory-management strategy.

[1]: The compiler inlines calls (which are on the same thread); it doesn't inline multiple monadic stages (which often entail some megamorphic callsite).

Re: Project Loom and Structured Concurrency

#90

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'm not sure I really think of function coloring as a "problem" ...

facebook is experimenting with auto differentiation for Kotlin and looks like it's adding a new "differentiable" function color -- https://ai.facebook.com/blog/paving-the-way-for-software-20-...

It looks very easy to reason about and use to me ... and i personally find async a similarly useful marker ... It's about being able to push constraints from caller arbitrarily far down the callee stack -- which is really not something that types support at all but provides for a very high confidence variety of constraint -- and high confidence constraints seem to me like they convey a ton of information.

I've been wondering actually whether "function colors" might actually just be a good way to create a whole variety of strong statically enforceable constraints for functions. It seems like they lead to very good and simple programmer mental models ...

Are there languages that offer "user definable" function colors? I can think of a lot of application domains that would be much better served by these kinds of constraints than oo or other type-centric approaches ... it would be ridiculously useful to be able to mark a function with the "MyDomainBusinessLogic" color and get assurances that such a method can only call other functions annotated with that color ... would provide an easy way to iterate on app specific abstractions provide compiler assistance for the communication of layering intent -- rather than a bunch of poorly specified words in documents that try to communicate layering intent to other developers -- in language that is either sufficiently precise as to be incomprehensible -- or sufficiently vague as to be subject to (mis)interpretation ...

Post reply on HN