[HN Gopher] Asynchrony is not concurrency
       ___________________________________________________________________
        
       Asynchrony is not concurrency
        
       Author : kristoff_it
       Score  : 294 points
       Date   : 2025-07-18 19:21 UTC (1 days ago)
        
 (HTM) web link (kristoff.it)
 (TXT) w3m dump (kristoff.it)
        
       | threatofrain wrote:
       | IMO the author is mixed up on his definitions for concurrency.
       | 
       | https://lamport.azurewebsites.net/pubs/time-clocks.pdf
        
         | tines wrote:
         | Can you explain more instead of linking a paper? I felt like
         | the definitions were alright.
         | 
         | > Asynchrony: the possibility for tasks to run out of order and
         | still be correct.
         | 
         | > Concurrency: the ability of a system to progress multiple
         | tasks at a time, be it via parallelism or task switching.
         | 
         | > Parallelism: the ability of a system to execute more than one
         | task simultaneously at the physical level.
        
           | threatofrain wrote:
           | Concurrency is the property of a program to be divided into
           | partially ordered or completely unordered units of execution.
           | It does not describe how you actually end up executing the
           | program in the end, such as if you wish to exploit these
           | properties for parallel execution or task switching. Or maybe
           | you're running on a single thread and not doing any task
           | switching or parallelism.
           | 
           | For more I'd look up Rob Pike's discussions for Go
           | concurrency.
        
             | tines wrote:
             | The article understands this.
        
               | threatofrain wrote:
               | > Concurrency: the ability of a system to progress
               | multiple tasks at a time, be it via parallelism or task
               | switching.
               | 
               | Okay, but don't go with this definition.
        
               | michaelsbradley wrote:
               | Is the hang up on _" at a time"_? What if that were
               | changed to something like _" in a given amount of time"_?
               | 
               | For single threaded programs, whether it is JS's event
               | loop, or Racket's cooperative threads, or something
               | similar, if Dt is small enough then only one task will be
               | seen to progress.
        
           | andsoitis wrote:
           | > Asynchrony: the possibility for tasks to run out of order
           | and still be correct.
           | 
           | Asynchrony is when things don't happen at the same time or in
           | the same phase, i.e. is the opposite of Synchronous. It can
           | describe a lack of coordination or concurrence in time, often
           | with one event or process occurring independently of another.
           | 
           | The correctness statement is not helpful. When things happy
           | asynchronously, you do not have guarantees about order, which
           | may be relevant to "correctness of your program".
        
             | w10-1 wrote:
             | > The correctness statement is not helpful
             | 
             | But... that's everything, and why it's included.
             | 
             | Undefined behavior from asynchronous computing is not worth
             | study or investment, except to avoid it.
             | 
             | Virtually all of the effort for the last few decades (from
             | super-scalar processors through map/reduce algorithms and
             | Nvidia fabrics) involves enabling non-SSE operations that
             | are correct.
             | 
             | So yes, as an abstract term outside the context of
             | computing today, asynchrony does not guarantee correctness
             | - that's the difficulty. But the only asynchronous
             | computing we care about offers correctness guarantees of
             | some sort (often a new type, e.g., "eventually
             | consistent").
        
           | Lichtso wrote:
           | Concurrency is parallelism and/or asynchrony, simply the
           | superset of the other two.
           | 
           | Asynchrony means things happen out of order, interleaved,
           | interrupted, preempted, etc. but could still be just one
           | thing at a time sequentially.
           | 
           | Parallelism means the physical time spent is less that the
           | sum of the total time spent because things happen
           | simultaneously.
        
             | jrvieira wrote:
             | careful: in many programming contexts parallelism and
             | concurrency are exclusive concepts, and sometimes under the
             | umbrella of async, which is a term that applies to a
             | different domain.
             | 
             | in other contexts these words don't describe disjoint sets
             | of things so it's important to clearly define your terms
             | when talking about software.
        
               | merb wrote:
               | Yeah concurrency is not parallelism.
               | https://go.dev/blog/waza-talk
        
               | Lichtso wrote:
               | That is compatible with my statement: Not all concurrency
               | is parallelism, but all parallelism is concurrency.
        
               | OkayPhysicist wrote:
               | What people mean by "concurrency is not parallelism" is
               | that they are different problems. The concurrency problem
               | is defining an application such that it has parts that
               | are not causally linked with some other parts of the
               | program. The parallelism problem is the logistics of
               | actually running multiple parts of your program at the
               | same time. If I write a well-formed concurrent system, I
               | shouldn't have to know or care if two specific parts of
               | my system are actually being executed in parallel.
               | 
               | In ecosystems with good distributed system stories, what
               | this looks like in practice is that concurrency is your
               | (the application developers') problem, and parallelism is
               | the scheduler designer's problem.
        
               | jlouis wrote:
               | SIMD is parallel execution with no concurrency.
        
               | Lichtso wrote:
               | yes, some people swap the meaning of concurrency and
               | asynchrony. But, almost all implementations of async use
               | main event loops, global interpreter lock, co-routines
               | etc. and thus at the end of the day only do one thing at
               | a time.
               | 
               | Therefore I think this definition makes the most sense in
               | practical terms. Defining concurrency as the superset is
               | a useful construct because you have to deal with the same
               | issues in both cases. And differentiating asynchrony and
               | parallelism makes sense because it changes the trade-off
               | of latency and energy consumption (if the bandwidth is
               | fixed).
        
             | michaelsbradley wrote:
             | Asynchrony also practically connotes nondeterminism, but a
             | single-threaded concurrent program doesn't _have_ to
             | exhibit nondeterministic behavior.
        
           | jkcxn wrote:
           | Not the OP, but in formal definitions like Communicating
           | Sequential Processes, concurrency means the possibility for
           | tasks to run out of order and still be correct, as long as
           | other synchronisation events happen
        
             | gowld wrote:
             | Concurrency implies asynchrony (two systems potentially
             | doing work at the same time withut waiting for each other),
             | but the converse is not true.
             | 
             | A single process can do work in an unordered (asynchronous)
             | way.
        
               | Zambyte wrote:
               | Parallelism implies concurrency but not does not imply
               | asynchrony.
        
           | ryandv wrote:
           | They're just different from what Lamport originally proposed.
           | _Asynchrony_ as given is roughly equivalent to Lamport 's
           | characterization of distributed systems as _partially
           | ordered_ , where some pairs of events can't be said to have
           | occurred before or after one another.
           | 
           | One issue with the definition for concurrency given in the
           | article would seem to be that no concurrent systems can
           | deadlock, since as defined all concurrent systems can
           | progress tasks. Lamport uses the word concurrency for
           | something else: "Two events are concurrent if neither can
           | causally affect the other."
           | 
           | Probably the notion of (a)causality is what the author was
           | alluding to in the "Two files" example: saving two files
           | where order does not matter. If the code had instead been
           | "save file A; read contents of file A;" then, similarly to
           | the client connect/server accept example, the "save"
           | statement and the "read" statement would not be concurrent
           | under Lamport's terminology, as the "save" causally affects
           | the "read."
           | 
           | It's just that the causal relationship between two tasks is a
           | different concept than how those tasks are composed together
           | in a software model, which is a different concept from how
           | those tasks are physically orchestrated on bare metal, and
           | also different from the ordering of events..
        
             | kazinator wrote:
             | The definition of asynchrony is bad. It's possible for
             | asynchronous requests to guarantee ordering, such that if a
             | thread makes two requests A and B in that order,
             | asynchronously, they will happen in that order.
             | 
             | Asynchrony means that the requesting agent is not blocked
             | while submitting a request in order to wait for the result
             | of that request.
             | 
             | Asynchronous abstractions may provide a synchronous way
             | wait for the asynchronously submitted result.
        
               | ryandv wrote:
               | > The definition of asynchrony is bad. It's possible for
               | asynchronous requests to guarantee ordering, such that if
               | a thread makes two requests A and B in that order,
               | asynchronously, they will happen in that order.
               | 
               | It's true that it's possible - two async tasks can be
               | bound together in sequence, just as with `Promise.then()`
               | et al.
               | 
               | ... but it's not _necessarily_ the case, hence the
               | _partial_ order, and the  " _possibility_ for tasks to
               | run out of order ".
               | 
               | For example - `a.then(b)` might bind tasks `a` and `b`
               | together asynchronously, such that `a` takes place, and
               | then `b` takes place - but after `a` has taken place, and
               | before `b` has taken place, there may or may not be other
               | asynchronous tasks interleaved between `a` and `b`.
               | 
               | The ordering between `a`, `b`, and these interleaved
               | events is not defined at all, and thus we have a partial
               | order, in which we _can_ bind `a` and `b` together in
               | sequence, but have no idea how these two events are
               | ordered in relation to all the other asynchronous tasks
               | being managed by the runtime.
        
               | kazinator wrote:
               | I mean that it's possible in the sense of being designed
               | in as a guarantee; that the async operations issued
               | against some API object will be performed in the order in
               | which they are submitted, like a FIFO queue.
               | 
               | I don't mean "promise.then", whereby the issuance of the
               | next request is gated on the completion of the first.
               | 
               | An example might be async writes to a file. If we write
               | "abc" at the start of the file in one request and "123"
               | starting at the second byte in the second requests, there
               | can be a guarantee that the result will be "a123", and
               | not "abc2", without gating on the first request
               | completing before starting the other.
               | 
               | async doesn't mean out of order; it means the request
               | initiator doesn't synchronize on the completion as a
               | single operation.
        
           | amelius wrote:
           | > Asynchrony: the possibility for tasks to run out of order
           | and still be correct.
           | 
           | Can't we just call that "independent"?
        
             | skydhash wrote:
             | Not really. There may be some causal relations.
        
               | amelius wrote:
               | Can you give an example?
        
               | skydhash wrote:
               | Locks, scheduling,... That introduce some synchronicity
               | and so some kind of order. But it's enforced on the
               | system and not a required mechanism.
        
               | amelius wrote:
               | But if I run "ls" on a machine, and another user runs
               | "ls" on the same machine, wouldn't you consider them
               | independent, even though the OS uses all kinds of locks
               | and what not under the hood?
        
           | ninetyninenine wrote:
           | Doesn't multiple tasks at the same time make it simultaneous?
           | 
           | I think there needs to be a stricter definition here.
           | 
           | Concurrency is the ability of a system to chop a task into
           | many tiny tasks. A side effect of this is that if the system
           | chops all tasks into tiny tasks and runs them all in a sort
           | of shuffled way it looks like parallelism.
        
           | sriram_malhar wrote:
           | The phrase "multiple tasks at a time" is ill-defined,
           | according to Lamport, because whose clock are you trusting.
           | 
           | For lamport concurrent does not mean what it means to us
           | colloquially or informally (like, "meanwhile"). Concurrency
           | in Lamport's formal definition is only about order. If one
           | task is dependent or is affected by another, then the first
           | is ordered after the second one. Otherwise, they are deemed
           | to be "concurrent", even if one happens years later or
           | before.
        
         | sapiogram wrote:
         | This is why I've completely stopped using the term, literally
         | everyone I talk to seems to have a different understanding. It
         | no longer serves any purpose for communication.
        
         | carodgers wrote:
         | The author is aware that definitions exist for the terms he
         | uses in his blog post. He is proposing revised definitions. As
         | long as he is precise with his new definitions, this is fine.
         | It is left to the reader to decide whether to adopt them.
        
           | WhitneyLand wrote:
           | He's repurposing asynchrony that's different from the way
           | most literature and many developers use it, and that shift is
           | doing rhetorical work to justify a particular Zig API split.
           | 
           | No thanks.
        
         | laserbeam wrote:
         | Keep in mind that you can't really express half the concepts in
         | lamport's papers in most languages. You don't really talk about
         | total and partial clock ordering when starting a thread. You
         | only really do it in TLA+ when designing a protocol.
         | 
         | That being said, I agree we don't need a new term to express
         | "Zig has a function in the async API that throws a compilation
         | error when you run in a non-concurrent execution. Zig let's you
         | say that." It's fine to so that without proposing new theory.
        
       | ioasuncvinvaer wrote:
       | Is there anything new in this article?
        
         | butterisgood wrote:
         | Perhaps not, but sometimes the description from a different
         | angle helps somebody understand the concepts better.
         | 
         | I don't know how many "monad tutorials" I had to read before it
         | all clicked, and whether it ever fully clicked!
        
         | gowld wrote:
         | Most gen ed articles on HN are not new ideas, just articles
         | that could be pages from a textbook.
        
       | ltbarcly3 wrote:
       | The argument about concurrency != parallelism mentioned in this
       | article as being "not useful" is often quoted and rarely a useful
       | or informative, and it also fails to model actual systems with
       | enough fidelity to even be true in practice.
       | 
       | Example: python allows concurrency but not parallelism. Well not
       | really though, because there are lots of examples of parallelism
       | in python. Numpy both releases the GIL and internally uses open-
       | mp and other strategies to parallelize work. There are a thousand
       | other examples, far too many nuances and examples to cover here,
       | which is my point.
       | 
       | Example: gambit/mit-scheme allows parallelism via parallel
       | execution. Well, kindof, but really it's more like python's
       | multiprocess library pooling where it forks and then marshals the
       | results back.
       | 
       | Besides this, often parallel execution is just a way to manage
       | concurrent calls. Using threads to do http requests is a simple
       | example, while the threads are able to execute in parallel
       | (depending on a lot of details) they don't, they spend almost
       | 100% of their time blocking on some socket.read() call. So is
       | this parallelism or concurrency? It's what it is, it's threads
       | mostly blocking on system calls, parallelism vs concurrency gives
       | literally no insights or information here because it's a
       | pointless distinction in practice.
       | 
       | What about using async calls to execute processes? Is that
       | concurrency or parallelism? It's using concurrency to allow
       | parallel work to be done. Again, it's both but not really and you
       | just need to talk about it directly and not try to simplify it
       | via some broken dichotomy that isn't even a dichotomy.
       | 
       | You really have to get into more details here, concurrency vs
       | parallelism is the wrong way to think about it, doesn't cover the
       | things that are actually important in an implementation, and is
       | generally quoted by people who are trying to avoid details or
       | seem smart in some online debate rather than genuinely problem
       | solving.
        
         | throwawaymaths wrote:
         | edit: fixed by the author.
        
           | ltbarcly3 wrote:
           | yes I'm agreeing with the article completely
        
             | throwawaymaths wrote:
             | maybe change 'this' to a non-pronoun? like "rob pikes
             | argument"
        
               | ltbarcly3 wrote:
               | updated, thanks for the feedback
        
               | throwawaymaths wrote:
               | thanks for clarifying!
        
         | chowells wrote:
         | The difference is quite useful and informative. In fact, most
         | places don't seem to state it strongly enough: Concurrency is a
         | programming model. Parallelism is an execution model.
         | 
         | Concurrency is writing code with the appearance of multiple
         | linear threads that can be interleaved. Notably, it's about
         | _writing code_. Any concurrent system could be written as a
         | state machine tracking everything at once. But that 's really
         | hard, so we define models that allow single-purpose chunks of
         | linear code to interleave and then allow the language,
         | libraries, and operating system to handle the details. Yes,
         | even the operating system. How do you think multitasking worked
         | before multi-core CPUs? The kernel had a fancy state machine
         | tracking execution of multiple threads that were allowed to
         | interleave. (It still does, really. Adding multiple cores only
         | made it more complicated.)
         | 
         | Parallelism is running code on multiple execution units. That
         | is _execution_. It doesn 't matter how it was written; it
         | matters how it executes. If what you're doing can make use of
         | multiple execution units, it can be parallel.
         | 
         | Code can be concurrent without being parallel (see async/await
         | in javascript). Code can be parallel without being concurrent
         | (see data-parallel array programming). Code can be both, and
         | often is intended to be. That's because they're describing
         | entirely different things. There's no rule stating code must be
         | one or the other.
        
           | ltbarcly3 wrote:
           | When you define some concepts, those definitions and concepts
           | should help you better understand and simplify the
           | descriptions of things. That's the point of definitions and
           | terminology. You are not achieving this goal, quite the
           | opposite in fact, your description is confusing and would
           | never actually be useful in understanding, debugging, or
           | writing software.
           | 
           | Stated another way: if we just didn't talk about concurrent
           | vs parallel we would have exactly the same level of
           | understanding of the actual details of what code is doing,
           | and we would have exactly the same level of understanding
           | about the theory of what is going on. It's trying to impose
           | two categories that just don't cleanly line up with any real
           | system, and it's trying to create definitions that just
           | aren't natural in any real system.
           | 
           | Parallel vs concurrent is a bad and useless thing to talk
           | about. It's a waste of time. It's much more useful to talk
           | about what operations in a system can overlap each other in
           | time and which operations cannot overlap each other in time.
           | The ability to overlap in time might be due to technical
           | limitations (python GIL), system limitations (single core
           | processor) or it might be intentional (explicit locking), but
           | that is the actual thing you need to understand, and parallel
           | vs concurrent just gives absolutely no information or
           | insights whatsoever.
           | 
           | Here's how I know I'm right about this: Take any actual
           | existing software or programming language or library or
           | whatever, and describe it as parallel or concurrent, and then
           | give the extra details about it that isn't captured in
           | "parallel" and "concurrent". Then go back and remove any
           | mention of "parallel" and "concurrent" and you will see that
           | everything you need to know is still there, removing those
           | terms didn't actually remove any information content.
        
             | chowells wrote:
             | Addition vs multiplication is a bad and useless thing to
             | talk about. It's a waste of time. It's much more useful to
             | talk about what number you get at the end. You might get
             | that number from adding once, or twice, or even more times,
             | but that final number is the actual thing you need to
             | understand and "addition vs multiplication" just gives
             | absolutely no information or insights whatsoever.
             | 
             | They're just different names for different things. Not
             | caring that they're different things makes communication
             | difficult. Why do that to people you intend to communicate
             | with?
        
             | gf000 wrote:
             | That leaves out the (partial) ordering of "operations".
             | Happens before/after/at the same time is just one important
             | primitive.
        
           | frollogaston wrote:
           | Async JS code is parallel too. For example, await
           | Promise.all(...) will wait on multiple functions at once. The
           | JS event loop is only going to interpret one statement at a
           | time, but in the meantime, other parts of the computer (file
           | handles, TCP/IP stack, maybe even GPU/CPU depending on the JS
           | lib) are actually doing things fully in parallel. A more
           | useful distinction would be, the JS interpreter is single-
           | threaded while C code can be multithreaded.
           | 
           | I can't think of anything in practice that's concurrent but
           | not parallel. Not even single-core CPU running 2 threads,
           | since again they can be using other resources like disk in
           | parallel, or even separate parts of the CPU itself via
           | pipelining.
        
             | filleduchaos wrote:
             | > A more useful distinction would be, the JS interpreter is
             | single-threaded while C code can be multithreaded.
             | 
             | ...this seems like a long way round to say "JS code is not
             | parallel while C code can be parallel".
             | 
             | Or to put it another way, it seems fairly obvious to me
             | that parallelism is a concept applied to _one 's own code_,
             | not all the code in the computer's universe. Other parts of
             | the computer doing other things has nothing to do with the
             | point, or "parallelism" would be a completely redundant
             | concept in this age where nearly every CPU has multiple
             | cores.
        
       | Retr0id wrote:
       | I don't get it - the "problem" with the client/server example in
       | particular (which seems pivotal in the explanation). But I am
       | also unfamiliar with zig, maybe that's a prerequisite. (I am
       | however familiar with async, concurrency, and parallelism)
        
         | koakuma-chan wrote:
         | Example 1
         | 
         | You can write to one file, wait, and then write to the second
         | file.
         | 
         | Concurrency not required.
         | 
         | Example 2
         | 
         | You can NOT do Server.accept, wait, and then do Client.connect,
         | because Server.accept would block forever.
         | 
         | Concurrency required.
        
           | tines wrote:
           | Oh, I see. The article is saying that async is required. I
           | thought it was saying that parallelism is required. The way
           | it's written makes it seem like there's a problem with the
           | code sample, not that the code sample is correct.
        
             | Retr0id wrote:
             | The article later says (about the server/client example)
             | 
             | > Unfortunately this code doesn't express this requirement
             | [of concurrency], which is why I called it a programming
             | error
             | 
             | I gather that this is a quirk of the way async works in
             | zig, because it would be correct in all the async runtimes
             | I'm familiar with (e.g. python, js, golang).
             | 
             | My existing mental model is that "async" is just a
             | syntactic tool to express concurrent programs. I think I'll
             | have to learn more about how async works in zig.
        
               | sunshowers wrote:
               | I think a key distinction is that in many application-
               | level languages, each thing you await exists autonomously
               | and keeps doing things in the background whether you
               | await it or not. In system-level languages like Rust (and
               | presumably Zig) the things you await are generally
               | passive, and only make forward progress if the caller
               | awaits them.
               | 
               | This is an artifact of wanting to write async code in
               | environments where "threads" and "malloc" aren't
               | meaningful concepts.
               | 
               | Rust does have a notion of autonomous existence: tasks.
        
               | Retr0id wrote:
               | Thanks, this clears things up for me.
               | 
               | I suppose I conflated "asynchrony" (as defined in the
               | article) and "async" as a syntax feature in languages I'm
               | familiar with.
        
               | m11a wrote:
               | I think that notion is very specific to Rust's design.
               | 
               | Golang for example doesn't have that trait, where the
               | user (or their runtime) must drive a future towards
               | completion by polling.
        
               | sunshowers wrote:
               | Right, but Go is an application-level language and
               | doesn't target environments where threads aren't a
               | meaningful concept. It's more an artifact of wanting to
               | target embedded environments than something specific to
               | Rust.
        
               | jayd16 wrote:
               | Thank you so much for the added context. This explains
               | the article very well.
        
           | jayd16 wrote:
           | But why is this a novel concept? The idea of starvation is
           | well known and you don't need parallelism for it to effect
           | you already. What does zig actually do to solve this?
           | 
           | Many other languages could already use async/await in a
           | single threaded context with an extremely dumb scheduler that
           | never switches but no one wants that.
           | 
           | I'm trying to understand but I need it spelled out why this
           | is interesting.
        
             | shikon7 wrote:
             | The novel concept is to make it explicit where a non-
             | concurrent scheduler is enough (async), and where it is not
             | (async-concurrent). As a benefit, you can call async
             | functions directly from a synchronous context, which is not
             | possible for the usual async/await, therefore avoiding the
             | need to have both sync and async versions of every
             | function.
             | 
             | And with green threads, you can have a call chain from
             | async to sync to async, and still allow the inner async
             | function to yield through to the outer async function. This
             | keeps the benefit of async system calls, even if the
             | wrapping library only uses synchronous functions.
        
       | danaugrs wrote:
       | Excellent article. I'm looking forward to Zig's upcoming async
       | I/O.
        
       | butterisgood wrote:
       | It's kind of true...
       | 
       | I can do a lot of things asynchronously. Like, I'm running the
       | dishwasher AND the washing machine for laundry at the same time.
       | I consider those things not occurring at "the same time" as
       | they're independent of one another. If I stood and watched one
       | finish before starting the other, they'd be a kind of synchronous
       | situation.
       | 
       | But, I also "don't care". I think of things being organized
       | concurrently by the fact that I've got an outermost orchestration
       | of asynchronous tasks. There's a kind of governance of
       | independent processes, and my outermost thread is what turns the
       | asynchronous into the concurrent.
       | 
       | Put another way. I don't give a hoot what's going on with your
       | appliances in your house. In a sense they're not synchronized
       | with my schedule, so they're asynchronous, but not so much
       | "concurrent".
       | 
       | So I think of "concurrency" as "organized asynchronous
       | processes".
       | 
       | Does that make sense?
       | 
       | Ah, also neither asynchronous nor concurrent mean they're
       | happening at the same time... That's parallelism, and not the
       | same thing as either one.
       | 
       | Ok, now I'll read the article lol
        
         | didibus wrote:
         | I think asynchronous as meaning out-of-sync, implies that there
         | needs to be synchronicity between the two tasks.
         | 
         | In that case, asynchronous just means the state that two or
         | more tasks that should be synchronized in some capacity for the
         | whole behavior to be as desired, is not properly in-sync, it's
         | out-of-sync.
         | 
         | Then I feel there can be many cause of asynchronous behavior,
         | you can be out-of-sync due to concurent execution or due to
         | parallel execution, or due to buggy synchronization, etc.
         | 
         | And because of that, I consider asynchronous programming as the
         | mechanisms that one can leverage to synchronize asynchronous
         | behavior.
         | 
         | But I guess you could also think of asynchronous as doesn't
         | need to be synchronized.
         | 
         | Also haven't read the article yet lol
        
         | criddell wrote:
         | > I consider those things not occurring at "the same time" as
         | they're independent of one another.
         | 
         | What would it take for you to consider them as running at the
         | same time then?
        
           | kristoff_it wrote:
           | That eventually the server and the client are able to
           | connect.
        
             | criddell wrote:
             | The dishwasher and washing machine?
        
       | throwawaymaths wrote:
       | a core problem is that the term async itself is all sorts of
       | terrible, synchronous usually means "happening at the same time",
       | is _not_ what is happening when you don 't use `async`
       | 
       | its like the whole flammable/inflammable thing
        
       | butterisgood wrote:
       | > Concurrency refers to the ability of a system to execute
       | multiple tasks through simultaneous execution or time-sharing
       | (context switching)
       | 
       | Wikipedia had the wrong idea about microkernels for about a
       | decade too, so ... here we are I guess.
       | 
       | It's not a _wrong_ description but it's incomplete...
       | 
       | Consider something like non-strict evaluation, in a language like
       | Haskell. One can be evaluating thunks from an _infinite_
       | computation, terminate early, and resume something else just due
       | to the evaluation patterns.
       | 
       | That is something that could be simulated via generators with
       | "yield" in other languages, and semantically would be pretty
       | similar.
       | 
       | Also consider continuations in lisp-family languages... or
       | exceptions for error handling.
       | 
       | You have to assume all things could occur simultaneously relative
       | to each other in what "feels like" interrupted control flow to
       | wrangle with it. Concurrency is no different from the outside
       | looking in, and sequencing things.
       | 
       | Is it evaluated in parallel? Who knows... that's a strategy that
       | can be applied to concurrent computation, but it's not required.
       | Nor is "context switching" unless you mean switched control flow.
       | 
       | The article is very good, but if we're going by the "dictionary
       | definition" (something programming environments tend to get only
       | "partially correct" anyway), then I think we're kind of missing
       | the point.
       | 
       | The stuff we call "asynchronous" is usually a subset of
       | asynchronous things in the real world. The stuff we treat as task
       | switching is a single form of concurrency. But we seem to all
       | agree on parallelism!
        
       | raluk wrote:
       | One thing that most languages are lacking is expressing lazy
       | return values. -> await f1() + await f2() and to express this
       | concurently requres manually handing of futures.
        
         | sedatk wrote:
         | Which languages do have such a thing?
        
           | Twey wrote:
           | I suppose Haskell does, as `(+) <$> f1 <*> f2`.
        
             | raluk wrote:
             | In there is also ApplicativeDo that works nicely with this.
             | do            x <- f1           y <- f2           return $
             | x + y
             | 
             | this is evaluated as applicative in same way.
        
           | steveklabnik wrote:
           | Rust does this, if you don't call await on them. You can then
           | await on the join of both.
        
             | sedatk wrote:
             | Is the "join" syntax part of the language?
        
               | tcfhgj wrote:
               | no
               | 
               | https://doc.rust-lang.org/std/future/macro.join.html
        
               | sedatk wrote:
               | Then it doesn't apply in this case.
        
               | tcfhgj wrote:
               | why?
        
               | deathanatos wrote:
               | Why is having it be syntax necessary or beneficial?
               | 
               | One might say "Rust's existing feature set makes this
               | possible already, why dedicate syntax where none is
               | needed?"
               | 
               | (...and I think that's a reasonably pragmatic stance,
               | too. Joins/selects are somewhat infrequent, the
               | impediments that writing out a join puts on the program
               | relatively light... what problem would be solved?
               | 
               | vs. `?`, which sugars a _common_ thing that non-dedicated
               | syntax can represent (a try! macro is sufficient to
               | replace ?) but for which the burden on the coder is much
               | higher, in terms of code readability  & writability.)
        
         | zmj wrote:
         | That's because f2's result could depend on whether f1 has
         | executed.
        
         | jayd16 wrote:
         | you mean like?                  await Join(f1(), f2())
         | 
         | Although more realistically                  Promise1 = f1();
         | Promise2 = f2();        await Join(Promise1, Promise2);
         | 
         | But also, futures are the expression of lazy values so I'm not
         | sure what else you'd be asking for.
        
           | raluk wrote:
           | This is what i hand in mind whit "manually handing of
           | futures". In this case you have to write
           | Promise1 = f1(); Promise2 = f2();        v1,v2 = await
           | Join(Promise1, Promise2);        return v1 + v2
           | 
           | I think this is just too much of synthactic noise.
           | 
           | On the other hand, it is necessary becase some of underlying
           | async calls can be order dependend.
           | 
           | for example                   await sock.rec(1) == 'A' &&
           | await sock.rec(1) == 'B'
           | 
           | checks that first received socket byte is A and second is B.
           | This is clearly order dependant that can't be executed
           | concurrently out of order.
        
             | jayd16 wrote:
             | I suppose you'd have to make
             | SumAsync(F1(),f2());
             | 
             | Buy it's kind of intractable, isn't it? Your language has
             | to assume order dependency or independency and specify the
             | other. Most seem to stick with lexical ordering implies
             | execution order.
             | 
             | I think some use curly brace scoping to break up
             | dependency. I want to say kotlin does something like this.
             | 
             | This is why they say async is a viral pattern but IMO
             | that's because you're adding specificity and function
             | coloring is necessary and good.
        
       | kobzol wrote:
       | The new Zig I/O idea seems like a pretty ingenious idea, if you
       | write mostly applications and don't need stackless coroutines. I
       | suspect that writing libraries using this style will be quite
       | error-prone, because library authors will not know whether the
       | provided I/O is single or multi-threaded, whether it uses evented
       | I/O or not... Writing concurrent/async/parallel/whatever code is
       | difficult enough on its own even if you have perfect knowledge of
       | the I/O stack that you're using. Here the library author will be
       | at the mercy of the IO implementation provided from the outside.
       | And since it looks like the IO interface will be a proper kitchen
       | sink, essentially an implementation of a "small OS", it might be
       | very hard to test all the potential interactions and combinations
       | of behavior. I'm not sure if a few async primitives offered by
       | the interface will be enough in practice to deal with all the
       | funny edge cases that you can encounter in practice. To support a
       | wide range of IO implementations, I think that the code would
       | have to be quite defensive and essentially assume the most
       | parallel/concurrent version of IO to be used.
       | 
       | It will IMO also be quite difficult to combine stackless
       | coroutines with this approach, especially if you'd want to avoid
       | needless spawning of the coroutines, because the offered
       | primitives don't seem to allow expressing explicit polling of the
       | coroutines (and even if they did, most people probably wouldn't
       | bother to write code like that, as it would essentially boil down
       | to the code looking like "normal" async/await code, not like Go
       | with implicit yield points). Combined with the dynamic dispatch,
       | it seems like Zig is going a bit higher-level with its language
       | design. Might be a good fit in the end.
       | 
       | It's quite courageous calling this approach "without any
       | compromise" when it has not been tried in the wild yet - you can
       | claim this maybe after 1-2 years of usage in a wider ecosystem.
       | Time will tell :)
        
         | ta8645 wrote:
         | > It will IMO also be quite difficult to combine stackless
         | coroutines with this approach
         | 
         | Maybe there will be unforeseen problems, but they have promised
         | to provide stackless coroutines; since it's needed for the WASM
         | target, which they're committed to supporting.
         | 
         | > Combined with the dynamic dispatch
         | 
         | Dynamic dispatch will only be used if your program employs more
         | than one IO implementation. For the common case where you're
         | only using a single implementation for your IO, dynamic
         | dispatch will be replaced with direct calls.
         | 
         | > It's quite courageous calling this approach "without any
         | compromise" when it has not been tried in the wild yet.
         | 
         | You're right. Although it seems quite close to what "Jai" is
         | purportedly having success with (granted with an implicit IO
         | context, rather than an explicitly passed one). But it's
         | arguable if you can count that as being in the wild either...
        
         | necovek wrote:
         | > I think that the code would have to be quite defensive and
         | essentially assume the most parallel/concurrent version of IO
         | to be used.
         | 
         | Exactly, but why would anyone think differently when the goal
         | is to support both synchronous and async execution?
         | 
         | However, if asynchrony is done well at the lower levels of IO
         | event handler, it should be simple to implemcent by following
         | these principles everywhere -- the "worst" that could happen is
         | that your code runs sequentially (thus slower), but not run
         | into races or deadlocks.
        
       | sedatk wrote:
       | Blocking async code is not async. In order for something to
       | execute "out of order", you must have an escape mechanism from
       | that task, and that mechanism essentially dictates a form of
       | concurrency. Async must be concurrent, otherwise it stops being
       | async. It becomes synchronous.
        
         | vitaminCPP wrote:
         | This is exactly what the article is trying to debunk.
        
         | didibus wrote:
         | If you need to do A and then B in that order, but you're doing
         | B and then A. It doesn't matter if you're doing B and then A in
         | a single thread, the operations are out of sync.
         | 
         | So I guess you could define this scenario as asynchronous.
        
           | jayd16 wrote:
           | So wait, is the word they mean by asynchrony actually the
           | word "dependency"?
        
             | Jtsummers wrote:
             | > So wait, is the word they mean by asynchrony actually the
             | word "dependency"?
             | 
             | No, the definition provided for asynchrony is:
             | 
             | >> Asynchrony: the possibility for tasks to run out of
             | order and still be correct.
             | 
             | Which is not dependence, but rather _in_ dependence.
             | Asynchronous, in their definition, is concurrent with no
             | need for synchronization or coordination between the tasks.
             | The contrasted example which is still concurrent but not
             | asynchronous is the client and server one, where the order
             | matters (start the server after the client, or terminate
             | the server before the client starts, and it won't work
             | correctly).
        
               | kristoff_it wrote:
               | > The contrasted example which is still concurrent but
               | not asynchronous is the client and server one
               | 
               | Quote from the post where the opposite is stated:
               | 
               | > With these definitions in hand, here's a better
               | description of the two code snippets from before: both
               | scripts express asynchrony, but the second one requires
               | concurrency.
               | 
               | You can start executing Server.accept and Client.connect
               | in whichever order, but both must be running "at the same
               | time" (concurrently, to be precise) after that.
        
               | Jtsummers wrote:
               | Your examples and definitions don't match then.
               | 
               | If asynchrony, as I quoted direct from your article,
               | insists that order doesn't matter then the client and
               | server are not asynchronous. If the client were to
               | execute before the server and fail to connect (the server
               | is not running to accept the connection) then your system
               | has failed, the server will run later and be waiting
               | forever on a client who's already died.
               | 
               | The client/server example is _not_ asynchronous by your
               | own definition, though it is concurrent.
               | 
               | What's needed is a fourth term, synchrony. Tasks which
               | are concurrent (can run in an interleaved fashion) but
               | where order between the tasks matters.
        
               | kristoff_it wrote:
               | > If the client were to execute before the server and
               | fail to connect (the server is not running to accept the
               | connection) then your system has failed, the server will
               | run later and be waiting forever on a client who's
               | already died.
               | 
               | From the article:
               | 
               | > Like before, the order doesn't matter: the client could
               | begin a connection before the server starts accepting
               | (the OS will buffer the client request in the meantime),
               | or the server could start accepting first and wait for a
               | bit before seeing an incoming connection.
               | 
               | When you create a server socket, you need to call
               | `listen` and after that clients can begin connecting. You
               | don't need to have already called `accept`, as explained
               | in the article.
        
               | jayd16 wrote:
               | > Which is not dependence, but rather independence
               | 
               | Alright, well, good enough for me. Dependency tracking
               | implies independency tracking. If that's what this is
               | about I think the term is far more clear.
               | 
               | > where the order matters
               | 
               | I think you misunderstand the example. The article
               | states:
               | 
               | > Like before, *the order doesn't matter:* the client
               | could begin a connection before the server starts
               | accepting (the OS will buffer the client request in the
               | meantime), or the server could start accepting first and
               | wait for a bit before seeing an incoming connection.
               | 
               | The one thing that must happen is that the server is
               | running while the request is open. The server task must
               | start and remain unfinished while the client task runs if
               | the client task is to finish.
        
               | Jtsummers wrote:
               | I'm about to reply to the author because his article is
               | actually confusing as written. He has contradictory
               | definitions relative to his examples.
        
             | didibus wrote:
             | Not necessarily, but I guess it depends how you define
             | "dependency".
             | 
             | For example, it might be partial ordering is needed only,
             | so B doesn't fully depend on A, but some parts of B must
             | happen after some parts of A.
             | 
             | It also doesn't imply necessarily that B is consuming an
             | output from A.
             | 
             | And so on.
             | 
             | But there is a dependency yes, but it could be that the
             | behavior of the system depends on both of them happening in
             | some partial ordering.
             | 
             | The difference is with asynchronous, the timing doesn't
             | matter, just the partial or full ordering. So B can happen
             | a year after A and it would eventually be correct, or at
             | least within a timeout. Or in other words, it's okay if
             | other things happen in between them.
             | 
             | With synchronous, the timings tend to matter, they must
             | happen one after the other without anything in-between. Or
             | they might even need to happen together.
        
         | nemothekid wrote:
         | Consider:                   readA.await         readB.await
         | 
         | From the perspective of the application programmer, readA
         | "block" readB. They aren't concurrent.
         | join(readA, readB).await
         | 
         | In this example, the two operations are interleaved and the
         | reads happen concurrently. The author makes this distinction
         | and I think it's a useful one, that I imagine most people are
         | familiar with even if there is no name for it.
        
           | sedatk wrote:
           | I think that confuses paradigm with configuration. Say, if
           | one thread waits on another to finish, that doesn't mean the
           | code suddenly becomes "single-threaded", it just means your
           | two threads are in a serialized configuration in that
           | instance. Similarly, when async code becomes serialized, it
           | doesn't cease to be async: the scaffolding to make it
           | concurrent is there, it's just unused in that specific
           | configuration.
           | 
           | For example, C# uses this syntax:                  await
           | readA();        await readB();
           | 
           | when you have these two lines, the first I/O operation still
           | yields control to a main executor during `await`, and other
           | web requests can continue executing in the same thread while
           | "readA()" is running. It's inherently concurrent, not in the
           | scope of your two lines, but in the scope of your program.
           | 
           | Is Zig any different?
        
       | tossandthrow wrote:
       | The author does not seem to have made any non-trivial projects
       | with asynchronicity.
       | 
       | All the pitfalls of concurrency are there - in particular when
       | executing non-idempotent functions multiple times before previous
       | executions finish, then you need mutexes!
        
         | ajross wrote:
         | > All the pitfalls of concurrency are there [in async APIs]
         | 
         | This is one of those "in practice, theory and practice are
         | different" situations.
         | 
         | There is nothing in the async world that looks like a parallel
         | race condition. Code runs to completion until it
         | deterministically yields, 100% of the time, even if the
         | location of those yields may be difficult to puzzle out.
         | 
         | And so anyone who's ever had to debug and reason about a
         | parallel race condition is basically laughing at that
         | statement. It's just not the same.
        
           | kibwen wrote:
           | _> Code runs to completion until it deterministically yields_
           | 
           | No, because async can be (quote often is) used to perform
           | I/O, whose time to completion does not need to be
           | deterministic or predictable. Selecting on multiple tasks and
           | proceeding with the one that completes first is an entirely
           | ordinary feature of async programming. And even if you don't
           | _need_ to suffer the additional nondeterminism of your OS 's
           | thread scheduler, there's nothing about async that says you
           | can't use threads as part of its implementation.
        
             | ajross wrote:
             | And I repeat, if you think effects from unpredictable I/O
             | completion order constitute equivalent debugging thought
             | landscapes to hardware-parallel races, I can only laugh.
             | 
             | Yes yes, in theory they're the same. That's the joke.
        
               | saghm wrote:
               | You're not reporting yourself, though, since you didn't
               | make it clear at all in your initial comment you were
               | talking about hardware. The previous comment you made
               | only mentioned "parallel data races" in a conversation
               | about software ecosystems, where both of those terms are
               | regularly used to describe things that occur. You're
               | laughing about dunking on people who you've run up to in
               | the middle of a football field; no one stopped you from
               | scoring because you didn't tell them that you're
               | apparently playing an entirely different game on your
               | own.
        
               | ajross wrote:
               | The term "hardware-parallel" was to clarify that I'm
               | talking about genuine parallelism. SMP bugs are 100%
               | software problems solved with software techniques. You
               | learn about them in software courses in school. They're
               | just much harder than getting your async rig to work.
        
               | saghm wrote:
               | You're probably right. I just don't think it was nearly
               | as clear what you were talking about before as you seemed
               | to be saying afterwards, so it came across as needlessly
               | smug when clarifying like you did here would have been
               | more than sufficient.
        
           | tossandthrow wrote:
           | Idk, I work in typescript though but I do have race
           | conditions that look exactly like those of concurrent
           | software.
           | 
           | In particular promise.all([f,f,f]) where I want to ensure
           | that I only Run the body of f a single time.
        
         | alerighi wrote:
         | You don't need mutex in async code, since there is no parallel
         | execution whatsoever. In fact languages that use async
         | programming as a first class citizen (JavaScript) don't even
         | have a construct to do them.
         | 
         | If you need to synchronize stuff in the program you can use
         | normal plain variables, since it's guaranteed that your task
         | will be never interrupted till you give control back to the
         | scheduler by performing an await operation.
         | 
         | In a way, async code can be used to implement mutex (or
         | something similar) themself: it's a technique that I use often
         | in JavaScript, to implement stuff that works like a mutex or a
         | semaphores with just promises to syncronize stuff (e.g. you
         | want to be sure that a function that itself does async
         | operations inside is not interrupted, it's possible to do so
         | with promises and normal JS variables).
        
           | Spivak wrote:
           | See this is what the OP is getting at, this is only true for
           | async implementations that don't have any parallelism. That
           | doesn't have to be the case, there's no reason that your
           | javascript runtime couldn't take                   await
           | foo()         await bar()
           | 
           | and execute them in two threads transparently for you. It
           | just happens, like the Python GIL, that it doesn't. Your JS
           | implementation actually already has mutexes because web
           | workers with shared memory bring true parallelization along
           | with the challenges that come with.
        
             | Dylan16807 wrote:
             | In the case of javascript, it's only allowed to do that
             | when you can't detect it, situations where foo doesn't
             | affect the output of bar. So _as far as pitfalls are
             | concerned_ it does one thing at a time. The rest is a
             | hidden implementation detail of the optimizer.
        
           | mikojan wrote:
           | https://developer.mozilla.org/en-
           | US/docs/Web/API/LockManager...
           | 
           | Please don't implement this yourself
        
           | saghm wrote:
           | > You don't need mutex in async code, since there is no
           | parallel execution whatsoever. In fact languages that use
           | async programming as a first class citizen (JavaScript) don't
           | even have a construct to do them.
           | 
           | This isn't even remotely true; plenty of languages have both
           | async and concurrency, probably more than ones that don't. C#
           | was the language that originated async/await, not JavaScript,
           | and it certainly has concurrency, as do Swift, Python. Rust,
           | and many more. You're conflating two independent proprieties
           | of JavaScript as language and incorrectly inferring a link
           | between them that doesn't actually exist.
        
         | laserbeam wrote:
         | Mutexes are part of the Io interface in zig. So is sleep,
         | select, network calls, file io, cancellation...
        
       | jayd16 wrote:
       | I kind of think the author simply pulled the concept of yielding
       | execution out of the definition of concurrency and into this new
       | "asynchrony" term. Then they argued that the term is needed
       | because without it the entire concept of concurrency is broken.
       | 
       | Indeed so, but I would argue that concurrency makes little sense
       | without the ability to yield and is therefore intrinsic to it.
       | Its a very important concept but breaking it out into a new term
       | adds confusion, instead of reducing it.
        
         | LegionMammal978 wrote:
         | I'd count pure one-to-one parallelism as a form of concurrency
         | that doesn't involve any yielding. But otherwise, I agree that
         | all forms of non-parallel concurrency have to be yielding
         | execution at some cadence, even if it's at the instruction
         | level. (E.g., in CUDA, diverging threads in a warp will
         | interleave execution of their instructions, in case one branch
         | tries blocking on the other.)
        
         | kristoff_it wrote:
         | >I kind of think the author simply pulled the concept of
         | yielding execution out of the definition of concurrency and
         | into this new "asynchrony" term.
         | 
         | Quote from the article where the exact opposite is stated:
         | 
         | > (and task switching is - by the definition I gave above - a
         | concept specific to concurrency)
        
           | jayd16 wrote:
           | Well I'm having a hell of a time understanding what this
           | article is trying to say. On a 3rd and 4th pass I think
           | perhaps they mean task (in)dependency tracking is a
           | fundamental concept. Independent tasks have "asynchrony."
           | (Can we just say dependency and independency?)
           | 
           | But even with that definition, it seems like the idea of
           | promises, task tracking, etc is well tread territory.
           | 
           | Then they conclude with how fire and forget tasks solve
           | coloring but isn't that just the sync-over-async anti-
           | pattern? I wouldn't be excited that my UI work stops to run
           | something when there are no more green threads but they seem
           | excited by it.
           | 
           | Anyway, I guess I got too distracted by the high concept
           | "this is a fundamental change in thinking" fluff of the
           | article.
        
         | omgJustTest wrote:
         | Concurrency does not imply yielding...
         | 
         | Synchronous logic does imply some syncing and yielding could be
         | a way to sync - which is what i expect you mean.
         | 
         | Asynchronous logic is concurrent without sync or yield.
         | 
         | Concurrency and asynchronous logic do not exist - in real form
         | - in von Neumann machines
        
           | gf000 wrote:
           | Could you expand on what you mean?
        
       | kazinator wrote:
       | Asynchrony, in this context, is an abstraction which separates
       | the preparation and submission of a request from the collection
       | of the result.
       | 
       | The abstraction makes it possible to submit multiple requests and
       | only then begin to inquire about their results.
       | 
       | The abstraction allows for, but does not require, a concurrent
       | implementation.
       | 
       | However, the intent behind the abstraction is that there be
       | concurrency. The motivation is to obtain certain benefits which
       | will not be realized without concurrency.
       | 
       | Some asynchronous abstractions cannot be implemented without some
       | concurrency. Suppose the manner by which the requestor is
       | informed about the completion of a request is not a blocking
       | request on a completion queue, but a callback.
       | 
       | Now, yes, a callback can be issued in the context of the
       | requesting thread, so everything is single-threaded. But if the
       | requesting thread holds a non-recursive mutex, that ruse will
       | reveal itself by causing a deadlock.
       | 
       | In other words, we can have an asynchronous request abstraction
       | that positively will not work single threaded;
       | 
       | 1 caller locks a mutex
       | 
       | 2 caller submits request
       | 
       | 3 caller unlocks mutex
       | 
       | 4 completion callback occurs
       | 
       | If step 2 generates a callback in the same thread, then step 3 is
       | never reached.
       | 
       | The implementation must use some minimal concurrency so that it
       | has a thread waiting for 3 while allowing the requestor to reach
       | that step.
        
         | brabel wrote:
         | Completely agree. The server/client example in the post was
         | just one example of a program not being able to make progress,
         | you've just gave another which cannot be solved the same way,
         | and I would bet there are many more that they will be
         | discovering over time. IMO when async is used, concurrency
         | needs to be ensured.
        
           | kazinator wrote:
           | It depends on the API. If the only way to obtain the result
           | of the asynchronous dispatch is a synchronous operation on a
           | completion queue, then there are no cases where a single-
           | threaded implementation will break, other than not providing
           | the performance.
           | 
           | However, we can argue that if there is only a synchronous
           | operation to collect the result, then it's not truly async.
           | Asynchrony should mean not only that we can initiate a
           | request without waiting for the result, but that the
           | completion can happen at any time.
        
       | ang_cire wrote:
       | > Asynchrony is not concurrency
       | 
       | This is what I tell my boss when I miss standups.
        
       | dvt wrote:
       | "Asynchrony" is a very bad word for this and we already have a
       | very well-defined mathematical one: commutativity. Some
       | operations are commutative (order does not matter: addition,
       | multiplication, etc.), while others are non-commutative (order
       | does matter: subtraction, division, etc.).                   try
       | io.asyncConcurrent(Server.accept, .{server, io});
       | io.async(Cient.connect, .{client, io});
       | 
       | Usually, ordering of operations in code is indicated by the line
       | number (first line happens before the second line, and so on),
       | but I understand that this might fly out the window in async
       | code. So, my gut tells me this would be better achieved with the
       | (shudder) `.then(...)` paradigm. It sucks, but better the devil
       | you know than the devil you don't.
       | 
       | As written, `asyncConcurrent(...)` is confusing as shit, and
       | unless you memorize this blog post, you'll have no idea what this
       | code means. I get that Zig (like Rust, which I really like fwiw)
       | is trying all kinds of new hipster things, but half the time they
       | just end up being unintuitive and confusing. Either implement
       | (async-based) commutativity/operation ordering somehow (like
       | Rust's lifetimes maybe?) or just use what people are already used
       | to.
        
         | ryandv wrote:
         | Strictly speaking commutativity is defined over (binary)
         | operations - so if one were to say that two async statements
         | (e.g. connect/accept) are commutative, I would have to ask,
         | "under what operation?"
         | 
         | Currently my best answer for this is the bind (>>=) operator
         | (including, incidentally, one of its instances, `.then(...)`),
         | but this is just fuzzy intuition if anything at all.
        
           | dvt wrote:
           | Commutative operations (all of them I think?) are trivially
           | generalized to n-ary operations (in fact, we do this via [?]
           | and [?], in the case of addition and multiplication,
           | respectively). You're right that the question of what
           | "operation" we're dealing with here is a bit hazy; but I'd
           | wager that it's probably in the family of the increment
           | operation (N++ === N + 1 = 1 + N) since we're constantly
           | evaluating the next line of code, like the head of a Turing
           | machine.
           | 
           | Edit: maybe it's actually implication? Since the previous
           | line(s) logically imply the next. L_0 - L_1 - L_2 - L_n?
           | Though this is non-commutative. Not sure, it's been a few
           | years since my last metalogic class :P
        
             | senderista wrote:
             | Generalizing an associative binary op to an n-ary op just
             | requires an identity element Id (which isn't always
             | obvious, e.g. Id_AND=true but Id_OR=false).
        
               | singularity2001 wrote:
               | Identity is nop / pass
        
               | JadeNB wrote:
               | > Generalizing an associative binary op to an n-ary op
               | just requires an identity element Id (which isn't always
               | obvious, e.g. Id_AND=true but Id_OR=false).
               | 
               | Only for n = 0, I think. Otherwise, generalizing
               | associative binary f_2 to f_n for all positive integers n
               | is easily done inductively by f_1(x) = x and f_{n +
               | 1}(x_1, ..., x_n, x_{n + 1}) = f_2(f_n(x_1, ..., x_n),
               | x_{n + 1}), with no need to refer to an identity. (In
               | fact, the definition makes sense even if f_2 isn't
               | associative, but is probably less useful because of the
               | arbitrary choice to "bracket to the left.")
        
             | dwattttt wrote:
             | Implication sounds right. With no further analysis, running
             | each line in order is correct (for whatever "order" is
             | defined by a language, let's assume imperative).
             | 
             | A compiler could recognise that e.g. L_2 doesn't depend on
             | L_1, and would be free to reorder them. And compilers do
             | recognise this in terms of data dependence of operations.
        
           | benreesman wrote:
           | It's a good intuition. This has been studied extensively, the
           | composition rule that is lax enough to permit arbitrary
           | effects but strict enough to guarantee this class of outcomes
           | is (>>=). We can keep trying to cheat this as long as we
           | want, but it's bind.
        
           | xscott wrote:
           | > "under what operation?"
           | 
           | You could treat the semicolon as an operator, and just like
           | multiplication over matrices, it's only commutative for a
           | subset of the general type.
        
             | ryandv wrote:
             | Right, exactly. It's been said that (>>=) is a programmable
             | semicolon.
             | 
             | [0] https://news.ycombinator.com/item?id=21715426
        
             | singularity2001 wrote:
             | or carrots return/new line For that matter
        
           | noduerme wrote:
           | `.then()` is ugly, `await` is pretty, but wouldn't the
           | critical part to guarantee commutivity less than guaranteed
           | order (in js) be the `Promise.all([])` part?
        
           | Nevermark wrote:
           | The "operator" in this case would be the CPU executing 2 or N
           | procedures (or functions).
           | 
           | Commutivity is a very light weight pattern, and so is
           | correctly applicable to many things, and at any level of
           | operation, as long as the context is clear.
        
           | jhanschoo wrote:
           | Under function composition `;`, where both the LHS and RHS
           | are viewed as functions operating on the whole environment
           | state.
        
             | ryandv wrote:
             | Right; though it's a special kind of function composition
             | (Kleisli composition) and often presented in a different
             | form (bind, >>=).
        
         | dooglius wrote:
         | Commutativity is a much weaker claim because one is totally
         | before or after the other. e.g. AB may commute with C so
         | ABC=CAB but it is not necessarily the case that this equals
         | ACB. With asynchrony you are guaranteed ABC=ACB=CAB. (There may
         | be an exisiting mathematical term for this but I don't know it)
        
           | dvt wrote:
           | You can prove three-term commutativity from two-term (I did
           | it years ago, I think it looked something like this[1]), so
           | the ordering doesn't matter.
           | 
           | [1] https://math.stackexchange.com/questions/785576/prove-
           | the-co...
        
             | Ar-Curunir wrote:
             | Strictly speaking this also requires associativity.
        
             | dooglius wrote:
             | I'm not talking about a universe where all elements
             | commute, I'm talking about a situation in which A, B, and C
             | do not necessarily commute but (AB) and C do. For a
             | rigorous definition: given X and Y from some semigroup G,
             | say X and Y are asynchronous if for any finite
             | decompositions X=Z_{a_1}Z_{a_2}...Z_{a_n} and
             | Y=Z_{b_1}Z_{b_2}...Z_{b_m} (with Z's in G) then for any
             | permutation c_1,...,c_{n+m} of a_1,...,a_n,b_1,...,b_m that
             | preserves the ordering of a's and the ordering of the b's
             | has XY=Z_{c_1}Z_{c_2}...Z_{c_{n+m}}. I make the following
             | claim: if G is commutative then all elements are
             | asynchronous, but for a noncommutative G there can exist
             | elements X and Y that commute (i.e. XY=YX) but X and Y are
             | not asynchronous.
        
               | JW_00000 wrote:
               | To give a concrete example, matrix multiplication is not
               | commutative in general (AB [?] BA), but e.g.
               | multiplication with the identity matrix is (AI = IA). So
               | AIB = ABI [?] BAI.
               | 
               | Or applied to the programming example, the statements:
               | 1. Server.accept         2. Client.connect         3.
               | File.write  # write to completely unrelated file
               | 
               | 123 = 312 [?] 321.
        
           | jhanschoo wrote:
           | I agree. T and U async with respect to each other means at
           | least that T and U can be broken down into tasks t1, t2, t3,
           | ... tn and u1, u2, ..., un, such that they can be interleaved
           | in any order, but typically we still require that the t tasks
           | are executed in sequential order. The divisions between the
           | tasks are where they give up control, e.g. as they wait for
           | data to be loaded into memory, or on a network call.
           | 
           | This is still a special case of what we mean by async wrt
           | each other, because depending on the interleaving at each
           | step and e.g. the data loaded into memory, the number of
           | tasks may change, but the idea is that they still eventually
           | terminate in a correct state.
        
         | brailsafe wrote:
         | > "Asynchrony" is a very bad word for this and we already have
         | a very well-defined mathematical one: commutativity.
         | 
         | I don't think it's sufficient to say that just because another
         | term defines this concept means it's a better or worse word.
         | "commutativity" feels, sounds, and reads like a mess imo.
         | Asynchrony is way easier on the palette
        
           | throwawaymaths wrote:
           | commutativity is also not correct, because 1) it means way
           | more things than just temporal ordering and 2) there are
           | cooky temporal ordering schemes you can come up with
           | (interleaving multiple async/awaits in weird time-dependent
           | ways) which aren't really describable in the simple
           | mathematical notion of commutativity.
        
         | hinkley wrote:
         | Asynchrony also allows for partial ordering. Two operations may
         | still need to be retired in a particular order without having
         | to execute in that order.
         | 
         | Subtraction for instance is not commutative. But you could
         | calculate the balance and the deduction as two separate queries
         | and then apply the results in the appropriate order.
        
         | ordu wrote:
         | _> As written, `asyncConcurrent(...)` is confusing as shit, and
         | unless you memorize this blog post, you 'll have no idea what
         | this code means. I get that Zig (like Rust, which I really like
         | fwiw) is trying all kinds of new hipster things, but half the
         | time they just end up being unintuitive and confusing. Either
         | implement (async-based) commutativity/operation ordering
         | somehow (like Rust's lifetimes maybe?) or just use what people
         | are already used to._
         | 
         | I can't agree. It is confusing, because you need to remember
         | the blog post, it wouldn't be confusing in the slightest if you
         | internalized the core idea. The question remains: is it worth
         | it to internalize the idea? I don't know, but what I do know is
         | some people will internalize it and try to do a lot of shit
         | with this in mind, and after a while we will be able to see
         | where this path leads to. At that point we will be able to
         | decide if it is a good idea or not.
         | 
         |  _> "Asynchrony" is a very bad word for this and we already
         | have a very well-defined mathematical one: commutativity._
         | 
         | It is risky to use "commutativity" for this. Zig has operators,
         | and some of them are commutative. And it will be confusing.
         | Like if I wrote `f() + g(). Addition is commutative, then Zig
         | is free to choose to run f() and g() in parallel. The order of
         | execution and commutativity are different things. Probably one
         | could tie them into one thing with commutative/non-commutative
         | operators, but I'm not sure it is a good idea, and I'm sure
         | that this is the completely different issue to experimenting
         | with asynchrony.
        
           | psychoslave wrote:
           | I'm not sure they are that different, you could just as well
           | store function calls in some constant each on its line then
           | addition the result on a third. This is only syntax, not
           | conceptual difference here. And on practical level, the
           | difference is that the operator can be directly matched with
           | some machine instruction, with operands being native data
           | type such as integer.
           | 
           | Still, you might then prefer a word as permutability, or
           | swappability.
        
         | delusional wrote:
         | > Usually, ordering of operations in code is indicated by the
         | line number
         | 
         | Except for loops which allow going backwards, and procedures
         | which allow temporarily jumping to some other locally linear
         | operation.
         | 
         | We have plenty of syntax for doing non-forwards things.
        
         | tbrownaw wrote:
         | > _Some operations are commutative (order does not matter:
         | addition, multiplication, etc.)_
         | 
         | Fun fact: order _does_ matter for addition. (When adding many
         | floating-point numbers with widely varying exponents.)
        
         | tsimionescu wrote:
         | > So, my gut tells me this would be better achieved with the
         | (shudder) `.then(...)` paradigm. It sucks, but better the devil
         | you know than the devil you don't.
         | 
         | The whole idea behind `await` is to make the old intuition work
         | without the ugliness of `.then()`. `f(); await g(); h()` has
         | exactly the expected execution ordering.
        
           | Yoric wrote:
           | Can confirm.
           | 
           | In JS, we designed `await` specifically to hide `.then()`,
           | just as we had designed `.then()` because callbacks made
           | tracking control flow (in particular errors) too complex.
        
             | psychoslave wrote:
             | How is that any better to have await? Any resources I might
             | consult on this?
        
               | Yoric wrote:
               | Well, one of the ways we "sold" async/await it to Google
               | was by showing how we could improve Promise-based tests.
               | 
               | I recall that one of our test suites was tens of
               | thousands of lines of code using `then()`. The code was
               | complicated enough that these lines were by and large
               | considered write-only, partly because async loops were
               | really annoying to write, partly because error-handling
               | was non-trivial.
               | 
               | I rewrote that test suite using `Task.spawn` (our
               | prototype for async/await). I don't have the exact
               | numbers in mind, but this decreased the number of LoC by
               | a factor of 2-3 and suddenly people could see the
               | familiar uses of loops and `try`/`catch`.
        
               | tsimionescu wrote:
               | Well, consider the difference between
               | a().then(() =>              b())          .then(() =>
               | c())
               | 
               | Compared to                 await a()       await b()
               | await c()
               | 
               | Even for this simple case I think it's much clearer. Then
               | look at a more complex case:                 for( i=0;
               | i<n; i++) {         await a(i) ;       }
               | 
               | Now try re-writing this with then() and see the
               | difference.
        
               | psychoslave wrote:
               | For the first one, it really feels like only a matter of
               | how you break lines. Sure there is also the matter of
               | anonymous function syntax, but
               | 
               | The latter is more fair, here is a possible solution:
               | const gen = (function* () {         for (let i = 0; i <
               | n; i++) yield a(i);       })();            const run =
               | next => !next.done && next.value.then(() =>
               | run(gen.next()));       run(gen.next());
               | 
               | Or something similar using reduce. But in both cases, it
               | illustrates the point, I guess.
               | 
               | But if we are at point we can introduce new
               | keywords/syntax in the language, it would just as well
               | possible to come with something like
               | a.chain(b, c)
               | 
               | In case you need to pass parameters
               | a.chain([b, p1, p2], c)
               | 
               | And for the latter case                 const indexes =
               | (function* () {         for (let i = 0; i < n; i++) yield
               | i;       })       a.through(indexes)
        
               | Yoric wrote:
               | Well, sure, if you have `yield`, you pretty much have
               | `await` already, as `await` is thin syntactic sugar on
               | top of `yield` in all languages other than OCaml.
        
         | alfiedotwtf wrote:
         | > Usually, ordering of operations in code is indicated by the
         | line number (first line happens before the second line, and so
         | on), but I understand that this might fly out the window in
         | async code
         | 
         | This isn't always true at the language level, and almost
         | certainly not at the CPU pipeline and microcode level.
         | 
         | Logic languages like Prolog will execute statements out of
         | order, by design. Other languages like Mercury use the IO monad
         | to signify serial operations
        
           | sdbrady wrote:
           | I'm not sure what you mean by "statements" in Prolog as it's
           | not a term the language defines. If you're referring to
           | clauses, it's not true that execution is unordered: the
           | Prolog interpreter attempts to unify a goal with clauses from
           | the knowledge base in the order they appear. This ordering is
           | semantically significant for control flow.
           | 
           | If instead you're referring to goals within the body of a
           | clause, this is also incorrect. Goals are evaluated strictly
           | left-to-right, and each must succeed before the next is
           | attempted. This evaluation order is likewise required and
           | observable, especially in the presence of side effects.
        
             | alfiedotwtf wrote:
             | > the Prolog interpreter attempts to unify a goal with
             | clauses from the knowledge base in the order they appear.
             | 
             | I was under the impression that when plugging holes during
             | unification, that these statements/clauses could happen in
             | any order just as you would like solving a crossword puzzle
        
       | skybrian wrote:
       | The way I like to think about it is that libraries vary in which
       | environments they support. Writing portable libraries that work
       | in any environment is nice, but often unnecessary. Sometimes you
       | don't care if your code works on Windows, or whether it works
       | without green threads, or (in Rust) whether it works without the
       | standard library.
       | 
       | So I think it's nice when type systems let you declare the
       | environments a function supports. This would catch mistakes where
       | you call a less-portable function in a portable library; you'd
       | get a compile error, indicating that you need to detect that
       | situation and call the function conditionally, with a fallback.
        
       | andrewstuart wrote:
       | That's word games.
       | 
       | If I launch 2 network requests from my async JavaScript and both
       | are in flight then that's concurrent.
       | 
       | Definition from Oxford Dictionary adjective 1. existing,
       | happening, or done at the same time. "there are three concurrent
       | art fairs around the city"
        
         | necovek wrote:
         | It is an attempt to delineate some classes of concurrent &
         | parallel programming challenges using a new term (really,
         | redefining it in the programming context to be a bit tighter).
         | 
         | Oxford dictionary holds no relevance here, unless it has took
         | over a definition from the field already (eg. look up "file": I
         | am guessing it will have a computer file defined there) -- but
         | as it lags by default, it can't have specific definitions being
         | offered.
        
         | jillesvangurp wrote:
         | The concepts of concurrency and parallelism are adjacent enough
         | that they are often confused. A lot of languages provide basic
         | concepts for both but use different frameworks for both. So the
         | difference really matters in that case. Or the frameworks are
         | just a bit low level and the difference really matters for that
         | reason (because you need to think about and be aware of
         | different issues).
         | 
         | I've been using Kotlin in the last few years. And while it is
         | not without issues, their co-routines approach is a thing of
         | beauty as it covers the whole of this space with one framework
         | that is designed to do all of it and pretty well thought
         | through. It provides a higher level approach in the form of
         | structured concurrency, which is what Zig is dancing around
         | here if I read this correctly (not that familiar with it so
         | please correct if wrong) and not something that a lot of
         | languages provide currently (Java, Javascript, Go, Rust,
         | Python, etc.). Several of those have work in progress related
         | to that though. I could see python going there now that they've
         | bit the bullet with removing the GIL. But they have a bit of
         | catching up to do. And several other languages provide ways
         | that are similarly nice and sophisticated; and some might claim
         | better.
         | 
         | In Kotlin, something being async or not is called suspending.
         | Suspending just means that "this function sometimes releases
         | control back to whatever called it". Typical moments when that
         | happens are when it does evented IO and/or when it calls into
         | other suspending functions.
         | 
         | What makes it structured concurrency is that suspend functions
         | are executed in a scope, which has something called a
         | dispatcher and a context (meta data about the scope). Kotlin
         | enforces this via colored "suspend" functions. Calling them
         | outside a coroutine scope is a compile error. Function colors
         | are controversial with some. But they works and it's simple
         | enough to understand. There's zero confusion on the topic.
         | You'll know when you do it wrong.
         | 
         | Some dispatchers are single threaded, some dispatchers are
         | threaded, and some dispatchers are green threaded (e.g. if on
         | the JVM). In Kotlin, a coroutine scope is obtained with a
         | function that takes a block as a parameter. That block receives
         | its scope as a context parameter (typically 'this'). When the
         | block exits, the whole tree of sub coroutines the scope had is
         | guaranteed to have completed or failed. The whole tree is
         | cancelled in case of an exception. Cancellation is one of the
         | nasty things many other languages don't handle very well. A
         | scope failure is a simple exception and if something cancelled,
         | that's a CancellationException. If this sounds complicated,
         | it's not that bad (because of Kotlin's DSL features). But
         | consider it necessary complexity. Because there is a very
         | material difference between how different dispatchers work.
         | Kotlin makes that explicit. But otherwise, it kind of is all
         | the same.
         | 
         | If inside a coroutine, you want to do two things
         | asynchronously, you simply call functions like launch or async
         | with another block. Those functions are provided by the
         | coroutine scope. If you don't have one, you can't call those.
         | That block will be executed by a dispatcher. If you want use
         | different threads, you give async/launch an optional new
         | coroutine scope with it's own dispatcher and context as a
         | parameter (you can actually combine these with a + operator).
         | If you don't provide the optional parameter, it simply uses the
         | parent scope to construct a new scope on the fly. Structured
         | concurrency here means that you have a nested tree of
         | coroutines that each have their own context and dispatchers.
         | 
         | A dispatcher can be multi threaded (each coroutine gets its own
         | thread) and backed by a thread pool, or a simple single
         | threaded dispatcher that just lets each coroutine run until it
         | suspends and then switches to the next. And if you are on the
         | JVM where green thread pools look just like regular thread
         | pools (this is by design), you can trivially create a green
         | thread pool dispatcher and dispatch your co routines to a green
         | thread. Note, this is only useful when calling into Java's
         | blocking IO frameworks that have been adapted to sort of work
         | with green threads (lots of hairy exceptions to that).
         | Technically, green threads have a bit more overhead for context
         | switching than Kotlin's own co-routine dispatcher. So use those
         | if you need it; avoid otherwise unless you want your code to
         | run slower.
         | 
         | There's a lot more to this of course but the point here is that
         | the resulting code looks very similar regardless of what
         | dispatchers you use. Whether you are doing things concurrently
         | or in parallel. The paradigm here is that it is all suspend
         | functions all the way down and that there is no conceptual
         | difference. If you want to fork and join coroutines, you use
         | functions like async and launch that return jobs that you can
         | await. You can map a list of things to async jobs and then call
         | awaitAll on the resulting list. That just suspends the parent
         | coroutine until the jobs have completed. Works exactly the same
         | with 1 thread or a million threads.
         | 
         | If you want to share data between your co-routines, you still
         | need to worry about concurrency issues and use locks/mutexes,
         | etc. But if your coroutine doesn't do that and simply returns a
         | value without having side effects on memory (think functional
         | programming here), things are quite naturally thread safe and
         | composable for structured concurrency.
         | 
         | There are a lot of valid criticisms on this approach. Colored
         | functions are controversial. Which I think is valid but not as
         | big of a deal in Kotlin as it is made out to be. Go's approach
         | is simpler but at the price of not dealing with failures and
         | cancellation as nicely. All functions are the same color. But
         | that simplicity has a price (e.g. no structured concurrency).
         | And it kind of shovels paralellism under the carpet. And it
         | kind of forces a lot of boiler plate on users by not having
         | proper exceptions and job cancellation mechanisms. Failures are
         | messy. It's simple. But at a price.
        
           | necovek wrote:
           | The idea behind Zig approach which is only being implemented
           | is to extend concurrent code to be executable outside the
           | coroutine context, making it trivially serializable when
           | that's possible (thus the networking example).
           | 
           | In general, the heavy lifting should always be moved to the
           | lower level infrastructure (compiler, standard library, RDBMS
           | system in case of ACID guarantees...) -- leaving developer
           | his brainspace for the business logic.
           | 
           | This requires minimum "function coloring", and I'd prefer if
           | Python took that approach instead.
        
         | messe wrote:
         | > If I launch 2 network requests from my async JavaScript and
         | both are in flight then that's concurrent.
         | 
         | That's because JS conflates the two. The async keyword in
         | JavaScript queues things for the event loop which is running in
         | a different thread, and progress will be made on them even if
         | they are never awaited. In Rust, for example, nothing will
         | happen _unless_ those Futures are awaited.
        
       | tekbog wrote:
       | There's a great old book on this if someone wants to check it:
       | Communicating Sequential Processes. From Hoare. Go channels and
       | the concurrent approach was inspired on this.
       | 
       | I also wrote a blog post a while back when I did a talk at work,
       | it's Go focused but still worth the read I think.
       | 
       | [0] https://bognov.tech/communicating-sequential-processes-in-
       | go...
        
       | bentleya wrote:
       | Non-blocking i/o isn't asynchrony and the author should know
       | better. Non-blocking io is a building block of asynchronous
       | systems -- it is not asynchony itself. Today's asynchronous
       | programming did not exist when non-blocking I/O was implemented
       | in Unix in the 80's.
        
       | nemothekid wrote:
       | I think I'm missing something here, but the most interesting
       | piece here is how would stackless coroutines work in Zig?
       | 
       | Since any function can be turned into a coroutine, is the
       | red/blue problem being moved into the compiler? If I call:
       | io.async(saveFileA, .{io});
       | 
       | Is that a function call? Or is that some "struct" that gets
       | allocated on the stack and passed into an event loop?
       | 
       | Furthermore, I guess if you are dealing with pure zig, then its
       | fine, but if you use any FFI, you can potentially end up issuing
       | a blocking syscall anyways.
        
         | laserbeam wrote:
         | I hope this is not a bad answer as I tried to understand what
         | stackless coroutines even are for the past week.
         | 
         | 1. Zig plans to annotate the maximum possible stack size of a
         | function call https://github.com/ziglang/zig/issues/23367 . As
         | people say, this would give the compiler enough information to
         | implemented stackless coroutines. I do not understand well
         | enough why that's the case.
         | 
         | 2. Allegedly, this is only possible because zig uses a single
         | compilation unit. You are very rarely dealing with modules that
         | are compiled independently. If a function in zig is not called,
         | it's not compiled. I can see how this helps with point 1.
         | 
         | 3. Across FFI boundaries this is a problem in every language.
         | In theory you can always do dumb things after calling into a
         | shared library. A random C lib can always spawn threads and do
         | things the caller isn't expecting. You need unsafe blocks in
         | rust for the same reason.
         | 
         | 4. In theory, zig controls the C std library when compiling C
         | code. In some cases, if there's only one Io implementation used
         | for example, zig could replace functions in the c std library
         | to use that io vtable instead.
         | 
         | Regardless, I kinda wish kristoff/andrew went over what
         | stackless coroutines are (for dummies) in an article at some
         | point. I am unsure people are talking about the same thing when
         | mentioning that term. I am happy to wait for that article until
         | zig tries to implement that using the new async model.
        
           | kibwen wrote:
           | _> As people say, this would give the compiler enough
           | information to implemented stackless coroutines._
           | 
           | Stackless coroutines require creating a structure big enough
           | to hold all the locals for the would-be function, but Zig
           | already has that information, along with Rust, and, by
           | definition, every other language that already supports
           | stackless coroutines.
           | 
           | The root of that linked issue is that Zig has some desire to
           | statically compute the _total_ stack usage of an entire
           | program, but the difficulty there is not in computing the
           | stack size for any given function (which is generally trivial
           | in most languages; supporting growable stack via something
           | like C 's `alloca` is the exception, not the rule). The
           | difficulty is that recursive functions can push an unbounded
           | number of function calls to the stack. So what Zig wants to
           | do is forbid recursion, even mutual recursion, unless you do
           | some kind of opt-in.
           | 
           | And this is where "Allegedly, this is only possible because
           | zig uses a single compilation unit" comes in, because
           | detecting mutual recursion is tricky, especially when virtual
           | dispatch gets involved.
           | 
           | But no, you don't need Zig-style whole-program compilation to
           | make that happen. All you need is 1) to be able to detect
           | mutual recursion within a single compilation unit (again,
           | stymied by virtual dispatch), and then 2) to prevent cyclical
           | dependencies between compilation units. Go and Rust both do
           | the latter, so they could get away with the same analysis,
           | assuming you can find a good solution for the former.
        
       | User23 wrote:
       | Asynchrony, parallelism, concurrency, and even deterministic
       | execution (albeit as a degenerate case) are all just species of
       | nondeterminism. Dijkstra and Scholten's work on the subject is
       | sadly under appreciated. And lest one thing this was ivory tower
       | stuff, before he was a professor Dijkstra was a systems engineer
       | writing operating systems on hilariously bad, by our standards,
       | hardware.
        
       | riwsky wrote:
       | "Permission for concurrency is not an obligation for concurrency.
       | Zig lets you explicitly permit-without-obligation, to support the
       | design of libraries that are polymorphic over a/sync 'function
       | color'."
        
       | Salgat wrote:
       | It's more accurate to say that callbacks and async/await can
       | facilitate concurrency.
        
       | reichstein wrote:
       | So "cooperative multitasking is not preemptive multitasking".
       | 
       | The typical use of the word "asynchronous" means that the
       | _language is single-threaded_ with cooperative multitasking
       | (yield points) and event based, and external computations may run
       | concurrently, instead of blocking, and will report result(s) as
       | events.
       | 
       | There is no point in having asynchrony in a multithreaded or
       | concurrent execution model, you can use blocking I/O and still
       | have progress in the program while that one execution thread is
       | blocked. Then you don't need the yield points to be explicit.
        
         | Yoric wrote:
         | While this is indeed the most common use, I'll bring as
         | counter-examples Rust (or C#, or F#, or OCaml 5+) that supports
         | both OS threads and async. OS threads are good for CPU-bound
         | tasks, async for IO-bound tasks.
         | 
         | The main benefit of having async (or Go-style M:N scheduling)
         | is that you can afford to launch as many
         | tasks/fibers/goroutines/... as you want, as long as you have
         | RAM. If you're using OS threads, you need to pool them
         | responsively to avoid choking your CPU with context-switches,
         | running out of OS threads, running out of RAM, etc. - hardly
         | impossible, but if you're doing more than just I/O, you can run
         | into interesting deadlocks.
        
           | wasmperson wrote:
           | > The main benefit of having async (or Go-style M:N
           | scheduling) is that you can afford to launch as many
           | tasks/fibers/goroutines/... as you want
           | 
           | Some have argued that the real solution to this problem is to
           | "just" fix OS threads. Rumor has it Google has done exactly
           | this, but keeps it close to their chest:
           | 
           | https://www.youtube.com/watch?v=KXuZi9aeGTw
           | 
           | https://lwn.net/Articles/879398/
           | 
           | Somewhat related and also by Google is WebAssembly Promise
           | Integration, which converts blocking code into non-blocking
           | code without requiring language support:
           | 
           | https://v8.dev/blog/jspi
           | 
           | I see a possible future where the "async/await" idea simply
           | fades away outside of niche use-cases.
        
             | Yoric wrote:
             | I think that PI will have a role, but I suspect that it can
             | quickly destroy the performance of wasm code in unexpected
             | ways.
             | 
             | As for fixing OS threads, indeed, this may very well change
             | the ecosystem, but many developers expect their code to be
             | cross-platform, so it might take a while before there is a
             | solution that works everywhere.
        
       | Yoric wrote:
       | Defining async is hard. And I'm writing this as one of the many
       | people who designed async in JavaScript.
       | 
       | I don't quite agree with the definition in this post: just
       | because it's async doesn't mean that it's correct. You can get
       | all sorts of user-land race conditions with async code, whether
       | it uses `async`/`await` (in languages that need/support it) or
       | not.
       | 
       | My latest formulation (and I think that it still needs work) is
       | that async means that the code is explicitly structured for
       | concurrency.
       | 
       | I wrote some more about the topic recently:
       | https://yoric.github.io/post/quite-a-few-words-about-async/ .
        
         | atoav wrote:
         | I am not deep into the matter but I would have given the
         | answer: Async code is making code that would have been blocking
         | non-blocking in a manner other stuff can still happen while it
         | is being completed.
         | 
         | Since I work a lot in embedded loops where long running
         | blocking snippets could literally break your I/O, lead to
         | visible/audible dropouts etc. this would be the obvious answer.
        
           | Yoric wrote:
           | But that's the thing: async, by itself, doesn't guarantee
           | that anything is non-blocking. For your fiber (or any other
           | kind of user-land abstraction) to be non-blocking, you MUST
           | ensure that it doesn't perform any blocking call.
           | 
           | All async does is give you (some of) the tools to make code
           | non-blocking.
        
             | atoav wrote:
             | Yeah, sure I mean in embedded-land any async snippet could
             | perform any number of things, like firing a delay command
             | that puts the whole processor to sleep.
             | 
             | This could potentially be avoided by clever enough
             | compilers or runtimes, but I am not sure whether that would
             | really be benefitial.
             | 
             | I am a fan of making things explicit, so the closer peoples
             | idea of what aync is and what it isn't matches reality the
             | better. Alternatively we should get the definition of what
             | async _should be_ clear first and then make the adjustment
             | to the abstractions so they give us the guarantees people
             | would natuarally assume come with that.
        
               | Yoric wrote:
               | Yeah, I'm insisting because I recently reviewed a PR with
               | async code calling blocking code, which made the entire
               | exercise pointless. And that was from an experienced dev.
               | 
               | There used to be a few compilers that used static
               | analysis to predict the cost of a call (where the cost of
               | I/O was effectively considered infinite) and in which you
               | could enforce that a branch only had a budget of N.
               | Modern architectures tend to mess up with any finite
               | value of N, but you could fairly easily adapt such
               | techniques to detect unbounded values.
        
               | YZF wrote:
               | The entire point might be to offload the blocking call
               | and do something else while it's blocking.
               | 
               | There's a style of "asynchronous programming" where
               | everything is designed to be non-blocking and there can
               | be asynchronous programming with blocking code. In fact
               | the first style can be emulated by offloading every
               | blocking call to a different thread/greenthread/fiber and
               | that's basically what's happening under the hood unless
               | there is some fundamental support for non-blocking at the
               | lower levels (sometimes all the way down to the
               | hardware).
        
             | quintu5 wrote:
             | _cries in Python asyncio_
        
               | Yoric wrote:
               | _sympathizes_
               | 
               | If you look on the bright side, it looks like free-
               | threading is approaching, and OCaml has demonstrated how,
               | by removing the GIL and adding exactly one primitive, you
               | can turn a powerful enough language into a
               | concurrency/parallelism powerhouse with minimal user-
               | visible changes!
        
             | YZF wrote:
             | You can run blocking code asynchronously from other code. I
             | think the parent means something like if you have a
             | blocking operation (function call e.g.) on a thread you can
             | create another thread and run that blocking operation
             | there, magically transforming a blocking call to something
             | that looks like a non-blocking call (thus allowing other
             | code to do something else instead of waiting)
        
         | twwwt wrote:
         | The important thing to me is to distinguish between the
         | abstract concept of asynchronism and how it can be implemented,
         | and by the latter I mean both at the abstract level of a
         | programming language and by technical coordination means in a
         | machine. For the abstract concept at the highest level, well,
         | it is just the dual of synchronism: two (or more) parties that
         | somehow need to work together (i.e., one has a dependency on
         | the other in a way that certain things need to happen _before_
         | another one can continue) are not synchronized, meaning that it
         | is not known or not defined _when_ the things that need to
         | happen _after_ something else will be done. Seen that way, this
         | definition is not hard. The hard thing can be the abstract
         | means designed in a language: the amount of cognitive effort it
         | takes in order to comprehended them and /or use them (in an
         | fault-free way).
        
           | Yoric wrote:
           | Largely agreed: async means that the code is structured in
           | such a manner that the only way to be certain that a task is
           | complete is to perform some rendez-vous. By extension, it
           | covers mechanisms to make this happen in your code.
        
         | rasmul wrote:
         | i am not even sure we should define async
         | 
         | it may be hard (it is) because it cannot be matched to one
         | thing
         | 
         | the question is: is it useful to define async? or event loop?
         | there must be tons of concepts i have no idea in the realm of
         | physical chips that make true parallelism possible
         | 
         | i am totally fine with "user finger" and "quickies", job queues
         | and blocking or non-blocking APIs
         | 
         | the finger symbolizes touch events and even mouse clicks and
         | keyboard or general user initiated events, which i have to
         | match to quickies which are very tiny (execution time)
         | blocking(!) jobs that will be queued by the browser
         | 
         | to reach my goals, i prefer non-blocking APIs because i can
         | discard some time consuming jobs to underlying systems and i
         | jsut write a quicky for what i want (store data in indexed db)
         | and what will happen if it succeeds or fails etc (different
         | quickies)
         | 
         | sync, async do not really help me, of course i have to
         | understand when others talk about it or i see or use (or my
         | preferred AI coder) async, but it just means non-blocking API
         | 
         | but again, the async programming model is actually writing very
         | much blocking quickies where the non-blocking nature is the
         | small, atomic nature (execution-time-wise) of the blocking jobs
         | I try to match to chaotic, non-deterministic events, triggered
         | by fingers or browsers or whatever
         | 
         | I actually dont care, just hope that the browser code uses
         | great concurrent models with cpp or rust or whatever and the
         | device has multiple executions units (os threads) and the os
         | scheduler does a great job, managing things whether there are
         | more execution units or just 1 available
         | 
         | async for me a not well defined concept and even if it was
         | somehow defined, i am not sure it would be useful to me
         | 
         | useful concepts are events, the blocking nature of jobs i write
         | in js, what my functions see (closure i guess), what runs as a
         | job if i use APIs and what runs as a different job after the
         | events the browser triggers (ready, error whatever)
         | 
         | even the name callback was extremely confusing because i really
         | thought back then that the code somehow stops there and waits
         | for a callback... no, it runs to the end of that section and
         | you really have to understand what other things run when it
         | "calls back" and what that code sees
         | 
         | to be honest, i think it is a mess and genius at the same
         | time... but understanding "async" or rather the model was
         | really difficult because i just dont think this means anything
         | 
         | it is actually very simple to understand with different
         | concepts like event, blocking job, job queue, non-blocking API
         | 
         | what i also find important to know what we are doing and what
         | others do like browser code, os etc... it is a bit like a cpp
         | code declares a concurrent model with a thrad but the os will
         | decide... in js, we use non-blocking api which implicitly
         | declares a probably concurrent model the browser or node or
         | whatever should use and i am sure they always do
         | 
         | the most important thing is to keep your jobs quick, probably
         | under 30-50ms and non-blocking API are great because your job
         | just declares the intent and done, and languages like cpp, rust
         | will declare the os that they want the actual task done
         | concurrently so even if the os has one real physical thread,
         | the UI will be responsive since the OS will switch between UI
         | code execution and "real task" execution like some networking
         | or database or whatever
         | 
         | but all an "async" programmer has to do is to create a great UX
         | model and match events to quickies
        
           | Yoric wrote:
           | Not entirely sure what you mean.
           | 
           | If you're writing that you don't need to understand how your
           | browser works, "just" to make things fast enough... well,
           | sure, go ahead.
           | 
           | But anybody who wants to graduate to a higher-level of
           | comprehension, will need to understand a bit better under the
           | hood.
        
       | jongjong wrote:
       | I think there's not much point trying to define these concepts as
       | there is no consensus about what they mean. Different people have
       | clear ideas about what each concept means but they just don't
       | agree.
       | 
       | It's like integration tests vs unit tests... Most developers
       | think they have a clear idea about what each one means, but based
       | on my experience there is very little consensus about where the
       | line is between unit test vs integration test. Some people will
       | say a unit test requires mocking or stubbing out all
       | dependencies, others will say that this isn't necessary; so long
       | as you mock out I/O calls... Others will say unit tests can make
       | I/O calls but not database calls or calls which interface with an
       | external service... Some people will say that if a test covers
       | the module without mocking out I/O calls then it's not an
       | integration test, it's an end-to-end test.
       | 
       | Anyway it's the same thing with asynchrony vs concurrency vs
       | parallelism.
       | 
       | I think most people will agree that concurrency can potentially
       | be achieved without parallelism and without asynchrony. For many
       | people, asynchrony has the connotation that it's happening in the
       | same process and thread (same CPU core). Some people who work
       | with higher level languages might say that asynchrony is a kind
       | of context switching (as it's switching context in the stack when
       | callbacks at called or promises resolved) but system devs will
       | say that context switching is more granular than that and not
       | constrained to the duration of specific operations, they'll say
       | it's a CPU level concept.
        
       | t43562 wrote:
       | I think it's a great idea to not have to have two libraries - so
       | its a "tick" from me for any idea that permits it.
       | 
       | The thing that bothers me in general about asynchronous code is
       | how you test it so that you know with some confidence that if it
       | passes the tests today you have replicated all the
       | scenarios/orderings that might happen in production.
       | 
       | You have this same problem with threads of course and I've always
       | found multithreaded programs to be much harder to write and
       | debug....such that I personally use threading only when I feel I
       | have to.
       | 
       | The actual problem with it is that caution is communicating it to
       | developers. I recently had to work on a python system where the
       | developers were obviously doing Javascript half the time. So ...
       | hooray.... they put out a huge changeset to make the thing
       | async....and threaded. Oddly enough none of them had ever heard
       | of the GIL and I got the feeling of being seen as an irritating
       | old bastard as I explained it to their blank stares. Didn't
       | matter. Threading is good. Then I pointed out that their tests
       | were now always passing no matter if they broke the code. Blank
       | stares. They didn't realise that mangum forced all background
       | tasks and async things to finish at the end of an HTTP request so
       | their efforts to shift processing to speed up the response were
       | for nothing.
       | 
       | Knowing things doesn't always matter if you cannot get other
       | people to see them.
        
         | Tanjim wrote:
         | That resonates. Testing asynchronous and multithreaded code for
         | all possible interleavings is notoriously difficult. Even with
         | advanced fuzzers or concurrency testing frameworks, you rarely
         | gain full confidence without painful production learnings.
         | 
         | In distributed systems, it gets worse. For example, when
         | designing webhook delivery infrastructure, you're not just
         | dealing with async code within your service but also network
         | retries, timeouts, and partial failures across systems. We ran
         | into this when building reliable webhook pipelines; ensuring
         | retries, deduplication, and idempotency under high concurrency
         | became a full engineering problem in itself.
         | 
         | That's why many teams now offload this to specialized services
         | like Vartiq.com (I'm working here), which handles guaranteed
         | webhook delivery with automatic retries and observability out
         | of the box. It doesn't eliminate the async testing problem
         | within your own code, but it reduces the blast radius by
         | abstracting away a chunk of operational concurrency complexity.
         | 
         | Totally agree though - async, threading, and distributed
         | concurrency all amplify each other's risks. Communication and
         | system design caution matter more than any syntax or library
         | choice.
        
           | Tainnor wrote:
           | > That's why many teams now offload this to specialized
           | services like Vartiq.com
           | 
           | It would be nice to add a disclaimer that this is a system
           | you're working on.
        
             | Tanjim wrote:
             | Thanks for pointing out. Edited. :)
        
         | kristoff_it wrote:
         | We plan to have in Zig a testing `Io` implementation that will
         | potentially use fuzzing to stress test your code under a
         | concurrent execution model.
         | 
         | That said, I think a key insight is that we expect most of the
         | library code out there to not do _any_ calls to `io.async` or
         | `io.asyncConcurrent`. Most database libraries for example don
         | 't need any of this and will still contain simple synchronous
         | code. But then that code will be able to be used by application
         | developers to express asynchrony at a higher level:
         | io.async(writeToDb)        io.async(doOtherThing)
         | 
         | Which makes things way less error prone and simpler to
         | understand than having async/await sprinkled all over the
         | place.
        
           | lll-o-lll wrote:
           | More powerful than a "fuzzing" test io would be a
           | deterministic test io. I.e., one you can tick forward the
           | various concurrent branches deterministically to prove that
           | various races are safely handled. This makes it possible to
           | capture all those "what if thread A executes this line first
           | then B is executed" etc. Something that is missing in most
           | concurrent frameworks.
        
       | ddingus wrote:
       | I do not see these definitions as quite right:
       | 
       | >Asynchrony: the possibility for tasks to run out of order and
       | still be correct.
       | 
       | I like this. Great addition and yes it was missing.
       | 
       | >Concurrency: the ability of a system to progress multiple tasks
       | at a time, be it via parallelism or task switching.
       | 
       | I would say here, be it multiprocessing or task switching.
       | 
       | >Parallelism: the ability of a system to execute more than one
       | task simultaneously at the physical level.
       | 
       | This is technically multiprocessing as expressed above.
       | 
       | So, what is the difference between parallelism and concurrency?
       | 
       | Parallel tasks are like shaders. It is the same task, running
       | many instances at the same time at the physical layer.
       | 
       | GPU devices are capable of parallel computing, for example.
       | 
       | Concurrent tasks are different tasks running at the same time at
       | the physical layer. Often, the data is different too. Say a
       | sprite engine running at the same time as a video display driver
       | on the physical layer.
       | 
       | The shaders can all be running the same code but are processing
       | different data elements, say each pixel having a position and is
       | part of a larger rendering.
       | 
       | A GPU is a massively parallel multiprocessor.
       | 
       | A Threadripper is a massive Concurrent multiprocessor. It can
       | also perform as a modest parallel multiprocessor.
       | 
       | The difference lies in what the various compute units can do and
       | what they are actually doing.
       | 
       | Put another way, a 10ghz single core CPU is not a multiprocessor.
       | It performs sequential computing and it can task switch to handle
       | the same task load as a lower clock rate multiprocessor would
       | handle.
       | 
       | A 10ghz multi core CPU is a concurrent multiprocessor, but is not
       | a GPU. It could run shaders on par with a lower clock GPU. BUT a
       | lower clock GPU cannot run a variety of tasks in the same way.
        
         | throwup238 wrote:
         | _> Parallel tasks are like shaders. It is the same task,
         | running many instances at the same time at the physical layer._
         | 
         | That's single instruction multiple data which I would argue is
         | an orthogonal concern. A better example of parallelism would be
         | FPGAs. All of the gates are switching all at the same time* and
         | you have to actually figure out how to synchronize the whole
         | lot to get anything useful out of them.
         | 
         | * PLL notwithstanding
        
           | ddingus wrote:
           | Yes, SIMD is a form of parallel computing. But that is not
           | the only form. A shader is one, and the difference is lots of
           | instructions and lots of data. It is just the same
           | instructions executing in all the instances.
           | 
           | Concurrent would be many tasks running at the same time with
           | each task containing different jnstructions on either the
           | same data, or different data.
        
         | gf000 wrote:
         | A single-threaded CPU can do concurrent processing just fine -
         | this is an important distinction compared to parallelism, and I
         | think you are muddying the two up a bit here.
        
       | yazantapuz wrote:
       | I allways think of async concurrency as state machines.
        
       | jlouis wrote:
       | A good way to gauge if "asynchrony" is a term we need is to test
       | if it is useful in other contexts than just a single language, or
       | a single concurrency design.
       | 
       | If it's needed to reason correctly in a wide set of concurrency
       | models, then I'd say it's going to be a useful addition. If not,
       | then I'd say it's not really worth using in the grander scheme of
       | things.
       | 
       | I.e., does this make any sense in Haskell, Erlang, OCaml, Scheme,
       | Rust, Go, .... ?(assuming we pick one of the many concurrency
       | models available in Haskell, Rust and OCaml).
       | 
       | More generally: if things are cooperatively scheduled, then
       | there's a need for attention to additional details. This is
       | because it's much easier for a bad piece of code to affect the
       | system as a whole, by locking it up, or generating latency-
       | problems. In a preemptively scheduled world, a large group of
       | problems disappear instantly, since you can't lock up the system
       | in the same way.
        
       | northhnbesthn wrote:
       | As a network programmer who has written a metric asston of
       | concurrent parallel asynchronous whatever-the-hell code [0], this
       | article is confusing. It seems like doing somersaults over a
       | leaky abstraction. The tool for the job is wrong and its
       | implementation is wrong if you can shit the bed this easily.
       | 
       | [0] debugging is fun precisely because it's amusing to watch
       | people be frightened of having to debug multi threaded hydras.
        
       | lowbloodsugar wrote:
       | >Asynchrony: the possibility for tasks to run out of order and
       | still be correct.
       | 
       | This is simply wrong. Asynchrony makes no claims about two
       | unrelated tasks, so "order" here is irrelevant and in terms of
       | each task, again, we expect the executions of a given task to be
       | in order. So this statement cannot be true under either
       | interpretation.
       | 
       | There's lots more wrong with the article as you would expect when
       | the starting premise is wrong.
        
       ___________________________________________________________________
       (page generated 2025-07-19 23:02 UTC)