5 ms·
> All you said is that cooperative scheduling requires making the scheduling points explicit while preemptive doesn't, but "explicit is better than implicit" is
by pron 3y ago
> All you said is that cooperative scheduling requires making the scheduling points explicit while preemptive doesn't, but "explicit is better than implicit" is a principle that a substantial portion of the programming community believes, and OP specifically expressed a preference for explicit scheduling points.
The problem is that the cooperative style makes mutual exclusion implicit and that's the important correctness assumption that is explicit in the preemptive style. The possible presence of scheduling point is the lack of an assumption, and therefore it shouldn't be explicit.
You're right that the issue is subjective, but it's subjectively determined in the overall design of the language. In Haskell, for example, there is an ever-present default assumption of lack of side effects and that assumption is at the core of the language, but if your language doesn't do that -- and Java doesn't and neither do C# or Python -- then the choice of cooperative scheduling isn't a good fit for the rest of the language. So perhaps I should have said that unless you design your language like Haskell (and unless you don't have the constraints the JS does), preemptive is the better fit for the rest of the language.
- lolinder 3y agoThere's nothing to stop you from setting up synchronization constructs like mutexes in a cooperatively-scheduled application. Kotlin bundles them with kotlinx.coroutines [0]. If anything, having possibly-concurrent methods explicitly marked makes mutual exclusion easier to reason about, because you know exactly which functions may have concurrency problems. 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? [0] https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/ https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-corout...
- 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 ago