3 ms·
I reject both assumption and conclusion. To the limited extent such control is needed, virtual threads with pluggable schedulers are superior in every way, and
by pron 6y ago
I reject both assumption and conclusion. To the limited extent such control is needed, virtual threads with pluggable schedulers are superior in every way, and certainly less intrusive. There aren't any "magic scheduler hints", either. You can assign a scheduler to each virtual thread, and that scheduler makes the relevant decisions. That's the same separation of concerns that native threads have, too.
The only place where this could be important is when dealing with Java code that cares about the identity of the native thread by querying it directly (because native code will see the native thread, but it's hidden from Java code). But, 1. such code is very rare, 2. almost all of it is in the JDK, which we control, and 3. even if some library somewhere does it, then that particular library will need to change to support virtual threads well, just as we require for most new big Java features.
Having said that, I don't claim async/await isn't a good solution for some platforms, and in some cases there's little choice. For example, Kotlin has no control of the JDK, which is necessary for lightweight threads, and languages with pointers into the stack as well as no GC and lots of FFI might find that a user-mode thread implementation is too costly. But I think that in cases where usermode threads can be implemented efficiently and in a way thar interacts well with the vast majority of code, they are clearly the preferable choice.
- lmm 6y ago> There aren't any "magic scheduler hints", either. You can assign a scheduler to each virtual thread, and that scheduler makes the relevant decisions. That's the same separation of concerns that native threads have, too. I'm thinking of when you need to "pin" to a particular native thread. In a native-threads + async/await language you have direct and visible control over where that happens. In a green-threads language it's going to involve magic. > Having said that, I don't claim async/await isn't a good solution for some platforms, and in some cases there's little choice. For example, Kotlin has no control of the JDK, which is necessary for lightweight threads, and languages with pointers into the stack as well as no GC and lots of FFI might find that a user-mode thread implementation is too costly. But I think that in cases where usermode threads can be implemented efficiently and in a way thar interacts well with the vast majority of code, they are clearly the preferable choice. A priori I agree. I expected green threads in Rust to work out too. I just can't get past having seen it fail to work out so many times. Maybe this time is different.
- pron 6y ago> In a green-threads language it's going to involve magic. No magic, just a custom scheduler for that particular thread. For example: Thread.builder().virtual(Executors.newFixedThreadPool(1)).factory(); would give you a factory for virtual threads that are all scheduled on top of one native thread (I didn't pick a specific one because I wanted to use something that's already in the JDK, but it's just as simple). > I expected green threads in Rust to work out too. I just can't get past having seen it fail to work out so many times. Maybe this time is different. Implementing usermode threads in Java and in Rust is very different as their constraints are very different. Here are some differences: Rust has pointers into the stack, Java doesn't; allocating memory (on stack growth) in Rust is costly and needs to be tracked, while in Java it's a pointer bump and tracked by the GC; Rust runs on top of LLVM, while Java controls the backend; Rust code relies on FFI far more than Java. So both the constraints and implementation challenges are too different between them to be comparable. Not to mention that they have different design goals: Rust, like all low-level languages, has low abstraction (implementation details are expressed in signatures) because, like all low-level languages, it values control more than abstraction. Java is a high-level language with a high level of abstraction -- there is one method call abstraction, and the JIT chooses the implementation; there is one allocation abstraction, and the GC and JIT choose the implementation -- and it values abstraction over control. So whereas, regardless of constraints, Rust happily lives with two distinct thread-like constructs, in Java that would be a failure to meet our goals (assuming, of course, meeting them is possible). There are many things that work well in high-level languages and not in low-level ones and vice versa, and that's fine because the languages are aimed at different problem domains and environments. Java should be compared to other high-level languages, like Erlang and Go, where userspace threads have been working very well, to the great satisfaction of users. Having said that, I suggest you take a look at coroutines in Zig, a language that, I think, brings a truly fresh perspective to low-level programming, and might finally be what we low-level programmers have been waiting for so many years.
- lmm 6y ago> No magic, just a custom scheduler for that particular thread. But there's a spooky-action-at-a-distance between that scheduler and the code that's running on the thread. Code that's meant to be pinned (and may behave incorrectly if not pinned) looks no different from any other code. > Not to mention that they have different design goals: Rust, like all low-level languages, has low abstraction (implementation details are expressed in signatures) because, like all low-level languages, it values control more than abstraction. Java is a high-level language with a high level of abstraction -- there is one method call abstraction, and the JIT chooses the implementation; there is one allocation abstraction, and the GC and JIT choose the implementation -- and it values abstraction over control. So whereas, regardless of constraints, Rust happily lives with two distinct thread-like constructs, in Java that would be a failure to meet our goals (assuming, of course, meeting them is possible). Java isn't positioned as a high-level, high-abstraction language and that's not, IME, the user community it has. It's a language that offers high performance at the cost of being verbose and cumbersome - witness the existence of primitive types, the special treatment of arrays, the memory-miserly default numeric types, the very existence of null. I've heard much more about people using Java for low-latency mechanical-sympathy style code than people using it for high-abstraction use cases like scripting or workbooks. It's always been advertised as a safer alternative to C++ - rather like Rust. (I'm all for trying to expand Java to be useful in other cases, but that's not grounds to sacrifice what it currently does well. For all the criticism Java attracts, it is undeniably extremely successful in its current niche)