Hacker News new | ask | show | jobs
by throw868788 1193 days ago
There are differences between implicit/pre-emptive and co-operative multitasking though and there is probably tradeoffs for each. For Java however, much of the existing API exposes Threads, and locks. How do you introduce better scale of existing threading code without too much porting/changes to existing codebases wihtout significant rewrites? The "we are where we are" problem exists a lot more in Java than say .NET/Node/Go/etc which IMO tips the scales more to this kind of approach.

I think that both approaches solve a lot of the same problems, but there is some problems that fit one paradigm better than the other and I don't think they are entirely mutually exclusive either. I've heard it mentioned with Loom it solves the async "colour" probglem however I think people "overblow" the color problem of the Async API too much. If most things are async it isn't really that painful. I actually like when something is marked "async" and I have to be careful how to use it. How is it async? Can the code run in parallel and join later? Is there race condition potential? Deadlock? How is it actually async, what's it sync vs async behaviour? How do I propogate the need to halt the async operation across multiple layers? Who owns the whole workflow, can I make sure the async context is propagated to that layer so they can cancel it for example?

Probably an unpopular opinion but knowing a method has async behavior, at the type level, could be a feature not a bug. It forces you to handle it at the sync/async junction and think about these things.

2 comments

> I think people "overblow" the color problem of the Async API too much. If most things are async it isn't really that painful.

There are two problems with async/await's cooperative multitasking:

1. The first is only relevant to languages that also have threads: it splits the APIs into two worlds with very similar semantics but disjoint syntactic "universes." You always need a separate API for either world and need to do everything twice.

2. Even when that is the only paradigm, it is less composable. When scheduling points are explicit, adding a scheduling point inside a subroutine requires changing all of its callers, transitively. In contrast, with non-cooperative multitasking, any subroutine in the hierarchy can individually choose to exclude interleaving (and in a finer-grained way) for atomic operations with various constructs (the simplest being locks). Since most operations need not be atomic and are independent, this is not only more composable and evolvable, but also a more reasonable default.

Async annotations in the vast majority of languages (possibly outside of JS and maybe rust) don't protect any useful invariant. You can still block in async code by calling any already existing blocking function not marked async, and async code can run in parallel when you have multiple underlying executors and shared memory.

If you design a language form scratch, async could be useful, but even then there would no need to annotate functions as they could be inferred. Generally effect systems seem a superior solution anyway.