3 ms·
> There's nothing to stop you from setting up synchronization constructs like mutexes in a cooperatively-scheduled application. Right, but assumptions of mutua
by pron 3y ago
> There's nothing to stop you from setting up synchronization constructs like mutexes in a cooperatively-scheduled application.
Right, but assumptions of mutual exclusion are still implicit, and so you must carefully analyse them any time you add a scheduling point. I.e. code may implicitly rely on mutual exclusion in cooperative scheduling but not in preemptive scheduling.
> Are you saying that there's a mechanism in Loom that somehow forces you to mark mutual exclusion which is absent from cooperatively-scheduled languages?
It's not a mechanism in virtual threads but in Java threads in general. Yes, to mark mutual exclusion you must employ some synchronisation construct explicitly, and that is not the case for cooperative scheduling where mutual exclusion is implicit, and that's what makes cooperative scheduling less composable.
- lolinder 3y agoI think we're talking past each other. Can we make this concrete? In what way is mutual exclusion implicit in Kotlin coroutines that is not also true in Java Loom? Here's Kotlin's explanation of how to do mutual exclusion [0], and here's a blog post explaining how mutual exclusion works in Java [1]. The main difference I see is that Java's `synchronized` is a full syntax construct while Kotlin uses its trailing-lambda syntax, but that's not semantically significant. What do you see as more explicit in the Java version? [0] https://kotlinlang.org/docs/shared-mutable-state-and-concurrency.html#mutual-exclusion https://kotlinlang.org/docs/shared-mutable-state-and-concurr... [1] https://www.baeldung.com/java-mutex https://www.baeldung.com/java-mutex
- pron 3y agoKotlin is not really a good example because it offers the worst of both worlds in this area: You must account for both preemptive and cooperative scheduling because both occur at the same time (they had no choice because they don't control the platform, only the frontend compiler). But let's look at a language like JS, that offers "clean" cooperative scheduling, and compare that to Java. An async JS function can contain code like this: x = 3; foo(); print(x); Now suppose that you want to perform some I/O operation in foo, which would require changing the function to: x = 3; await foo(); print(x); But is that allowed or does our function depend on x (which is shared among tasks in some way) not being changed between the first and third lines? Does the original function rely on the implicit mutual exclusion for its correct operation or not? In Java, on the other hand, if x is shared among some tasks then, regardless of what foo does, if the method depends on x not changing then the code must be written as: try (xGuard.lock()) { x = 3; foo(); print(x); } finally { xGuard.unlock(); } So adding a scheduling point to foo doesn't matter because if our method has any mutual exclusion assumptions it must express them explicitly (in practice, if x is guarded, then the guarding will often be encapsulated somehow, but the point remains). In JS that's not the case, so the addition of a scheduling point requires a careful analysis of all callers.
- lolinder 3y agoDoes Java actually prevent this code from compiling if `x` is shared? x = 3; foo(); print(x); If not, then I fail to see what the difference is with your JS example. It's still on the developer to keep track of whether x may change (and therefore whether a mutex is required), with the added disadvantage that there's no `async` keyword to clue you in that there might be a problem. You seem to be describing a style guide rule that Java projects should adopt, not a language feature, and there's no particular reason the same style guide couldn't be adopted another language if required.
- pron 3y ago> Does Java actually prevent this code from compiling if `x` is shared? Why would it prevent it from compiling? Whether it's right or wrong depends on what the programmer has in mind. If they rely on a mutual exclusion constraint, they must specify it. > It's still on the developer to keep track of whether x may change (and therefore whether a mutex is required), with the added disadvantage that there's no `async` keyword to clue you in that there might be a problem. No, because in Java, the author of this method has to decide what they want. In JS, when the author of foo decides to change it they need to analyse what the authors of the callers had in mind. The point is that the presence of a scheduling point or not is not just informative but has an impact on the correctness of the code. > You seem to be describing a style guide rule that Java projects should adopt, not a language feature No, it's a language feature. In Java the default is "scheduling point allowed." If you want to exclude it, you have to be explicit. This approach makes composing code easier than the opposite default unless your entire language is built around an even stronger constraint (like Haskell).
- lolinder 3y ago> In JS, when the author of foo decides to change it they need to analyse what the authors of the callers had in mind. No, because they already committed to not having any concurrent code in that method as part of the function contract. Making a function async is a breaking change (the return type changes to Promise<T>). If the author can't avoid the breaking change, then it would indeed force the caller to update their call site, cluing them in that the code may now run in parallel and mutual exclusion should be considered. There's no guesswork at what the callers are doing because you had an explicit contract with them that there would be no concurrency. If a Java method is passed an object by reference and subsequently decides to modify it in a thread instead of inline, that information is not propagated up to the callers, so they would have either had to guess that the library might change to a concurrent model and make the object thread-safe ahead of time, or they would have to read the release notes and then go find the call sites that need fixing. EDIT: And again, I'm not critiquing your decision to use this model for Java. On the balance I believe it was the right move, because as you note it's the model that was already extensively used in Java. I'm just arguing that preemptive is not objectively better, it depends on what existing code is already doing and on what you're trying to accomplish with the feature.