Re: Coroutines, goroutines

Jan Moringen <[email protected]> Thu, 26 Feb 2026 20:43:01 +0000
Newsgroups gmane.lisp.steel-bank.devel
Message-ID <[email protected]>
Hi Charles,

thanks for the background info.

Kind regards,
Jan

On Thu, 2026-02-26 at 13:50 +0000, Charles Zhang wrote:
> Jan:
> 
> Most of the questions you posed were discussed at ELS24 when the
> initial proof of concept work was done. The lightning talk references
> touches a bit on the specific design choices (but mostly focuses on
> showing something that works), but we basically came to the same
> conclusions for what people want that Anthony came to regarding
> stackful coroutines/fibers and treatment of dynamic variables, which
> is similar to the green threading model in cmu cl. See the Lua model
> for a very similar point in the design space.
> Anthony:
> Fibers being able to migrate between threads is an interesting choice
> though, and pretty advanced. What would be a reasonable default for
> users here in terms of number of carriers per fiber?
> 
> On Thursday, February 26, 2026, 1:56 PM, Anthony Green
> <[email protected]> wrote:
> 
> Hi Jan,
> I was saving some of this for when I was ready to present my
> implementation, but I leakedit early, so here are some answers to
> your thoughtful questions:
> The programming model is Java Project Loom's "virtual threads"
> adapted to Common Lisp.
> The core idea: fibers are lightweight cooperative threads with their
> own stacks that
> multiplex onto carrier OS threads. From the programmer's perspective,
> a fiber looks and
> acts like a thread -- you write ordinary sequential code, call
> ordinary blocking I/O,
> and the runtime handles the rest. No function coloring, no
> async/await, no special
> monadic style. This is the key property that motivated the design.
> 
> Coroutines vs. continuations: These are stackful coroutines, not
> first-class
> continuations. Each fiber gets its own mmap'd control stack (default
> 256KB) and binding
> stack (16KB). There are no heap-allocated continuation objects. The
> stack frames
> themselves are the continuity mechanism -- on yield, the stack is
> preserved in place; on
> resume, execution continues from the exact point of suspension. This
> was a deliberate
> choice: first-class delimited continuations are a different (and
> substantially harder)
> feature. Fibers solve the concurrent-I/O-without-coloring problem
> directly. Someone who
> wants delimited continuations for backtracking solvers would need a
> different mechanism,
> and I don't think fibers should try to be that.
> 
> Lexical variables: No special handling needed. Lexical variables live
> on the fiber's
> control stack and are naturally preserved across yields. Closures
> captured within a
> fiber work exactly as expected.
> 
> Dynamic variables: Each fiber maintains its own dynamic bindings. On
> yield, the fiber's
> current TLS values are saved to an overlay array and the carrier
> thread's original
> values are restored. On resume, the fiber's values are written back.
> The binding stack
> entries are never modified by the scheduler -- they remain intact for
> normal Lisp
> unbinding semantics. So (let ((*x* 1)) (fiber-yield) *x*) sees 1
> after resuming, and the
> carrier thread's binding of *x* is undisturbed while the fiber is
> suspended. This is
> analogous to how threads don't inherit dynamic bindings from their
> parent -- fibers are
> independent.
> 
> UNWIND-PROTECT: Works correctly. The fiber's catch block and unwind-
> protect block chain
> pointers are saved on yield and restored on resume. Cleanup forms do
> NOT run when a
> fiber suspends -- they run when the fiber eventually exits the
> protected form, either
> normally or via error. This matches the expected semantics:
> suspension is not unwinding.
> 
> Pinning (your DYNAMIC-WIND question): A fiber can be "pinned"
> via (with-fiber-pinned () ...), which prevents yielding inside the
> dynamic extent of the body. If a pinned fiber encounters a blocking
> primitive (mutex, condition-wait, I/O), it falls through to the OS
> blocking path instead of yielding. This is for holding resources that
> can't survive ayield (ffi). The *pinned-blocking-action* variable
> controls whether this situation warns, errors, or silently falls
> through.
> 
> Blocking I/O, locks, SLEEP: SBCL's standard blocking primitives
> (grab-mutex,
> condition-wait, wait-until-fd-usable) are fiber-aware. When called
> from within a fiber,
> they transparently yield with a wake-condition predicate instead of
> blocking the carrier
> thread. The scheduler polls these predicates and resumes fibers when
> their conditions
> are met. For I/O specifically, the scheduler uses epoll (Linux) or
> kqueue (BSD) for
> efficient multiplexing. fiber-sleep yields with a time-based deadline
> predicate.
> Existing code using standard CL I/O and SBCL threading primitives
> becomes fiber-aware
> automatically when called within a fiber context -- no code changes
> required.
> 
> Multi-carrier scheduling: Fibers aren't limited to a single OS
> thread. The run-fibers
> API accepts a :carrier-count parameter that spawns multiple carrier
> threads with a
> work-stealing scheduler (Chase-Lev deques). Each carrier owns a local
> deque -- push/pop
> from the bottom for locality, thieves steal from the top for
> fairness. This means fibers
> get both the lightweight concurrency model and actual parallelism
> across cores, which
> is again directly analogous to how Loom's virtual threads are
> scheduled onto a
> ForkJoinPool. A fiber may migrate between carriers across yields,
> which the runtime
> handles transparently (updating thread pointers, TLS overlays, GC
> roots, etc.).
> I welcome comments and questions, and I'm open to advice or
> suggestions.
> Thanks!AG
> On Thu, Feb 26, 2026 at 7:12 AM Jan Moringen <[email protected]>
> wrote:
> 
> I don't think it is my place anymore to comment on whether or with
> which tools such a feature should be implemented in SBCL but I would
> like to add one consideration regarding the process: Except for the
> first message, the discussion has focused on tools used for the
> implementation and details like stack size or abstraction for machine
> code generation.
> 
> The original message (and the quoted message) was about requirements
> of
> potential users. Those requirements mentioned a programming model for
> asynchronous I/O without "coloring" functions and also delimited
> continuations with backtracking in solvers as a possible application.
> 
> I feel the discussion should start with the envisioned programming
> model, (concurrent) evaluation semantics and user interface of the
> continuation/fiber/coroutine feature rather than the details of one
> particular implementation that happens to be available and close to
> finished. In particular as the original messages were talking about
> CL
> in general not SBCL. I have the impression that a lot of decisions
> have
> to be made (implicitly or, better, explicitly): 
>  * coroutines vs. continuations
>  * Closing over lexical variables (only applies to the continuation
>    model, i guess)
>  * Interaction with dynamic variables (are dynamic binding stacks
>    concatenated when invoking a continuation in a context that has
> its
>    own bindings?)
>  * Interaction with UNWIND-PROTECT when coroutines/continuations are
>    suspended
>  * DYNAMIC-WIND whether pinning
>  * Interaction with blocking I/O, locks, waiting for processes and
>    threads, SLEEP, etc.
> 
> If this discussion has already happened in a space I'm not aware of,
> I
> apologize for the noise.
> 
> Kind regards,
> Jan
> 
> 
> 


_______________________________________________
Sbcl-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/sbcl-devel