> [!tip] Concurrency without Parallelism
- [coroutines in go](https://research.swtch.com/coro)
- [[Temporal]]'s coroutine support - any blocking operation provided by the Temporal SDK suspends the execution.
# Implementation Ideas
- Stackless coroutine - this is the most robust implementation.
- coroutines become re-entrant state machine, similar to [[JavaScript|JS]] polyfills.
- "Stackful" / Stack-swapping implementation.
- if the operating system / execution environment supports it, it can be implemented without modifying the code.
- problems: 1. requires more cycles, and 2. some environments may not support it.
# Revisiting Coroutines
> [!quote] Classification: https://dl.acm.org/doi/pdf/10.1145/1462166.1462167
> This description of coroutines corresponds to the common perception of the concept, but leaves open relevant issues with respect to a coroutine construct. Apart from the capability of keeping state, we can identify three main issues that distinguish different kinds of coroutine facilities:
>
> —the control-transfer mechanism, which can provide *symmetric* or *asymmetric* coroutines;
> —whether coroutines are provided in the language as *first-class* objects, which can be freely manipulated by the programmer, or as constrained constructs;
> —whether a coroutine is implemented as a *stackful* construct, that is, whether it is able to suspend its execution from within nested calls.
> [!note]
> the "stackful" coroutines are not natively supported; in Python, `yield from` is needed to explicitly
# Comparison
## compared to [[Future|Async-Await]]
async-await is effectively a restricted generators (and earlier versions of them were implemented via generators); every `await` can be converted to `yield`, and start with a special trampoline-like structure to await the result.
## Generator
Generators are weaker form of coroutines. Generators create a separate execution context that can be suspended and resumed, but it can only do so at the initial stack frame. [[Python]]'s `yield` is a good example.
There are ways around this; `yield from` allows generators to be chained, so that suspension / resumption can happen deeper; however, this creates a chain of generators, and only can be done selectively against the code path you control.
For places where generators are used (ex: tree traversal), this limitation is minor and `yield from` is not too different from a coroutine.
# Versus Threads and Preemptive multitasking
[[JavaScript]]'s event model proved that cooperative multitasking is quite potent; in fact, in the "normal" computing, this is has more strengths than weaknesses.
- we are rarely starved for CPU.
- cooperative multitasking with a single thread of execution eliminates a large class of [[Concurrency]] bugs. Namely; low level data race and corruption from it is a non-issue.
- as long as the flow of execution is given away, we can assume that the operation is "atomic".
- CPU intensive operations can be fanned out to separate processes (python) or separate worker (JS's web worker). In this case, CPU intensive operations are seen as async operation with some latency.
- Both model forbids memory sharing and efficiency gain from it, forcing a lot of copies.
- Efficient data structures (i.e. structs not JSON) would allow for more efficient data passing. I.e. copying, not parsing.
> [!thought]
> Most `string` implementations requiring heap allocation is quite annoying.
# Push vs Pull model
Great thing about coroutines / async-await is that it allows the push- and pull- model of programming.
By pushing, I mean, you call the function and you push the parameters - i.e. `f(x)` - x is pushed to f.
By pulling; value is provided, and it's up to another function to pull the value - i.e. `yield x` - x is pulled from the function.
these design decisions (ex: push vs pull iterators) cannot usually operate interchangeably. However, the coroutines allow this to happen well;
## Example
- external iterators can turn into internal iterators by looping and calling the function, i.e.
- [x] TODO: update the internal-to-external. ✅ 2025-08-24
```python
def etoi(iter, f):
for elem in iter:
f(elem)
def itoe(canMap, f):
s = asyncio.Semaphore(1)
output = None
async def f(elem):
await s.acquire()
ouptut = elem
s.acquire()
iter.aeach(f)
loop: # TODO; exit condition.
yield output
output = None
s.release()
```