… as for blocking/non-blocking code… How do you distinguish, as a caller, blocking from long-running computation? And what to do about it anyway?
Even if our compilers were smart enough to detect possible blocking behavior in a called function, that still leaves my networking code prone to errors resulting from non-blocking, but long-running subroutines. > On Dec 28, 2025, at 05:46, David McClain <[email protected]> > wrote: > > Ahem… > > I believe that this “Color of your Code” issue is what drove the invention of > Async/Await. > > Frankly, I find that formalism horrid, as it divorces the actual runtime > behavior from the code, which itself is written in a linear style. > > My own solution is to fall back to the simplest possible Async model which is > Conventional Hewitt Actors, and a single shared communal event FIFO queue to > hold messages. > > But that does indeed offer a different color from our more typical > Call/Return architecture. > > My solution for the conundrum has been that you want to use Call/Return where > it shines - the innards of math libraries for example, and then use Async > coding to thread together Leggo Block subsystems that need coordination, > e.g., CAPI GUI code with computation snippets. > > Maybe I incorrectly find Async/Await a disgusting pretense? > > - DM > >> On Dec 28, 2025, at 05:17, [email protected] wrote: >> >> Hi, >> >> Yes, mailboxes get you a long way. However, some nuances got a bit lost in >> this thread (and I apologise that I contributed to this). >> >> Something that is very relevant to understand in the Go context: Go channels >> are not based on pthreads, but they are based around Go’s own tasking model >> (which of course are in turn based on pthreads, but’s not that relevant). >> Go’s tasking model is an alternative to previous async programming models, >> where async code and sync code had to be written in different programming >> styles - that made such code very difficult to write, read and refactor. (I >> believe >> https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ >> is the text that made that insight popular.) >> >> In Go, async code looks exactly the same as sync code, and you don’t even >> have to think about that distinction anymore. This is achieved by ensuring >> that all potentially blocking operations are effectively not blocking, but >> instead play nicely with the work-stealing scheduler that handles Go’s >> tasking model. So, for example, if a task tries to take a lock on a mutex, >> and that is currently not possible, the task gets swapped out and replaced >> by a different task that can continue its execution. This integration exists >> for all kinds of potentially blocking operations, including channels. >> >> With pthreads, a lock / mailbox / etc. that blocks can have the >> corresponding pthread replaced by another one, but that is much more >> expensive. Go’s tasks are handled completely in user space, not in kernel >> space. (And work stealing gives a number of very beneficial guarantees as >> well.) >> >> This nuance may or may not matter in your application, but it’s worth >> pointing out nonetheless. >> >> It would be really nice if Common Lisp had this as well, in place of a >> pthreads-based model, because it would solve a lot of issues in a very >> elegant way... >> >> Pascal >> >>> On 27 Dec 2025, at 18:45, David McClain <[email protected]> >>> wrote: >>> >>> Interesting about SBCL CAS. >>> >>> I do no use CAS directly in my mailboxes, but rely on Posix for them - both >>> LW and SBCL. >>> >>> CAS is used only for mutation of the indirection pointer inside the 1-slot >>> Actor structs. >>> >>> Some implementations allow only one thread inside an Actor behavior at a >>> time. I have no restrictions in my implementations, so that I gain true >>> parallel concurrency on multi-core architectures. Parallelism is automatic, >>> and lock-free, but requires careful purely functional coding. >>> >>> Mailboxes in my system are of indefinite length. Placing restrictions on >>> the allowable length of a mailbox queue means that you cannot offer >>> Transactional behavior. But in practice, I rarely see more than 4 threads >>> running at once. I use a Dispatch Pool of 8 threads against my 8 CPU Cores. >>> Of course you could make a Fork-Bomb that exhausts system resources. >>> >>>> On Dec 27, 2025, at 10:18, Manfred Bergmann <[email protected]> >>>> wrote: >>>> >>>> >>>> >>>>> Am 27.12.2025 um 18:00 schrieb David McClain >>>>> <[email protected]>: >>>>> >>>>>> I've reached the conclusion that if you have first-class functions and >>>>>> the ability to create FIFO queue classes, you have everything you need. >>>>>> You don't need Go channels, or operating system threads, etc. Those are >>>>>> just inefficient, Greenspunian implementations of a simpler idea. In >>>>>> fact, you can draw diagrams of Software LEGO parts, as mentioned by dbm, >>>>>> just with draw.io and OhmJS and a fairly flexible PL. [I'd be happy to >>>>>> elaborate further, but wonder if this would be appropriate on this >>>>>> mailing list] >>>>> >>>>> >>>>> This is essentially what the Transactional Hewitt Actors really are. We >>>>> use “Dispatch” threads to extract messages (function args and function >>>>> address) from a community mailbox queue. The Dispatchers use a CAS >>>>> protocol among themselves to effect staged BECOME and message SENDS, with >>>>> automatic retry on losing CAS. >>>>> >>>>> Messages and BECOME are staged for commit at successful exit of the >>>>> functions, or simply tossed if the function errors out - making an >>>>> unsuccessful call into an effective non-delivery of a message. >>>>> >>>>> Message originators are generally unknown to the Actors, unless you use a >>>>> convention of providing a continuation Actor back to the sender, embedded >>>>> in the messages. >>>>> >>>>> An Actor is nothing more than an indirection pointer to a functional >>>>> closure - the closure contains code and local state data. The indirection >>>>> allows BECOME to mutate the behavior of an Actor without altering its >>>>> identity to the outside world. >>>>> >>>>> But it all comes down to FIFO Queues and Functional Closures. The >>>>> Dispatchers and Transactional behavior is simply an organizing principle. >>>> >>>> >>>> Yeah, that’s exactly what Sento Actors >>>> (https://github.com/mdbergmann/cl-gserver/) are also about. >>>> Additionally, one may notice is that Sento has a nice async API called >>>> ’Tasks’ that’s designed after the Elixir example >>>> (https://mdbergmann.github.io/cl-gserver/index.html#SENTO.TASKS:@TASKS%20MGL-PAX:SECTION). >>>> On another note is that Sento uses locking with Bordeaux threads (for the >>>> message box) rather than CAS, because the CAS implementations I tried >>>> (https://github.com/cosmos72/stmx and an CAS based mailbox implementation >>>> in SBCL) were not satisfactory. The SBCL CAS mailbox being extremely fast >>>> but had a high idle CPU usage, so I dropped it. >>>> >>>> >>>> Cheers >>> >> >
