Async & Concurrency Models — Cross-Language Comparison

This note compares the async + concurrency models a programmer actually picks between — OS threads vs green threads vs async/await coroutines vs actors vs CSP channels vs STM vs reactive streams vs event loop vs structured concurrency vs virtual threads vs fiber — across all 53 deep language notes in the library. It’s a more operational view than _compare_concurrency (which covers the broader threads/STM/actors taxonomy); here the lens is “what async primitive do I reach for to handle 10⁴+ concurrent I/O operations, and what does it cost?” The closing decision tree maps latency + throughput + scale target onto the right primitive.

See also

1. The seven families

FamilyWhat you writeSchedulerStackLanguages
OS threads (1:1)std::thread, pthread_create, Thread::newkernelper-thread (typ 1–8 MB)C, C++, Java (until v21), Rust std::thread, C# native, Swift legacy, MATLAB
Green threads / M:Ngo fn(), spawn fn, Erlang spawnruntimesmall stackful (KB; growable)Go, Erlang, Elixir, Gleam, Haskell (forkIO), Pony
Async/await coroutines (stackless)async fn, await, futuresevent looptiny (state-machine struct)Python asyncio, Rust Tokio, JS event loop, C# Task, Kotlin coroutines, Swift, Dart, F#, Nim, Zig (proposed)
Actorsactor, Akka Actor, gen_serverruntime mailbox dispatcherper-actor heapErlang, Elixir, Akka (Scala/Java), Pony, Orleans (.NET), Swift actor
CSP channelsgo ch <- v, core.async chan, crossbeam::channelscheduler + channel queuemixedGo, Clojure core.async, Rust crossbeam, Crystal, Ocaml domainslib
STMatomically { ... }; refs / transactionsruntime + lognormalHaskell (canonical), Clojure, Scala (Akka STM legacy), Pony
Reactive streamsObservable.map().filter().subscribe()reactive schedulervariesRxJava, RxSwift, Combine, Reactor, Mutiny, RxJS, Akka Streams

Cross-cutting primitives below also matter (event loop, thread pool + work-stealing, structured concurrency, virtual threads, fiber).

2. OS threads — the kernel-backed primitive

A 1:1 thread of execution managed by the kernel. Preemptive scheduling, kernel-allocated TLB + stack, syscall-cheap context switch (~1 μs) but heavy creation (~100 μs) and large stack (default 1–8 MB so 1,000 threads = several GB virtual memory).

LanguageAPISchedulerDefault stackNotesLinked note
Cpthread_create, Win32 CreateThreadkernel8 MB (Linux)manual lifecycle; signal interactions trickyc
C++std::thread, std::jthread (C++20)kernelplatform defaultjthread joins on destruct; std::stop_token for cooperative cancellationcpp
Ruststd::thread::spawn, thread::Builderkernel2 MB defaultSend/Sync trait-checked at compile time; JoinHandle::join() returns Resultrust
JavaThread, ExecutorService, ForkJoinPoolkernel (until J21)1 MB (Linux)Thread::ofPlatform for kernel thread (J21+); Thread::ofVirtual for virtualjava
[[Languages/csharp|C#]]Thread class; Task on ThreadPoolkernel1 MBTask is preferred; Thread for fine controlcsharp
SwiftThread legacy; GCD DispatchQueue; Swift Concurrency since 5.5kernel512 KBThread deprecated for new code; use Taskswift
RubyThread.newGIL until 3.0 Ractor1 MBuntil 3.0 Ruby threads were essentially GIL-bound; Ractor (3.0+) is true parallelismruby
Pythonthreading.ThreadGIL until 3.13t (PEP 703)8 MBGIL-bound for CPU; 3.13t free-threaded build is the major shiftpython
Perlthreads (ithreads)kernelshared by copydiscouraged; fork preferredperl
JuliaThreads.@spawnkernel poolvariestrue threads; --threads=N at startjulia
MATLABparfeval + Parallel Computing Toolboxkernelvariesrequires PCT licencematlab
Adatask (Ada built-in) + protectedkernel or runtimevariestasks are first-class language entities; rendezvous semanticsada
FortranOpenMP directives; coarrays for distributedkernel + MPIvariesmostly directive-drivenfortran
Mojoparallelize + DPC++-style; async fn laterruntimevariesearly; targeting GPU + SIMD parallelism over thread parallelismmojo

When to use: CPU-bound work that needs preemption, real OS-level scheduling, integration with C/native libraries that demand a real thread. Native threads are fine up to a few hundred concurrent units; beyond that, virtual threads or async/await scale better.

3. Green threads / M:N — runtime-multiplexed

Many lightweight threads scheduled onto few OS threads by a userspace runtime. Cheap (<2 KB stack), cheap context switch (~100 ns), can host millions of them.

LanguagePrimitiveSchedulerStackNotesLinked note
Gogo fn() (goroutine)runtime M:N; netpoller + work-stealing2 KB growablethe gold standard; 1M goroutines per process commongo
Erlangspawn(fn) (process)BEAM scheduler233 bytes initial, per-process heapper-process isolation; up to 100M+ processes; preemptive (reduction-counted)erlang
Elixirspawn, Task.asyncBEAM (Erlang VM)as Erlangsame primitives + sugarelixir
Gleamprocess.startBEAMas Erlangtyped Erlang lineagegleam
HaskellforkIO, forkOS, asyncGHC RTS M:N1 KB initial; growableSTM + MVar + TVar for synchronization; very cheaphaskell
Ponyactorruntime work-stealingper-actor heapactor IS the green threadpony
Crystalspawn { ... } (Fiber)runtime8 MB stack but copy-on-writespawned via spawn; await via Channelcrystal
Nimthreads OR spawn (threadpool) OR asyncvariesvariesmixes modelsnim
Luacoroutines (asymmetric); luaproc lib for multi-OS-threaduserlandsmallcoroutines are stackful but single-threaded by defaultlua

Go’s goroutines + work-stealing + netpoller (epoll/kqueue/IOCP) are the canonical end-state. Go programs routinely run 10⁵–10⁶ goroutines. Erlang/BEAM goes further — single-node WhatsApp servers ran 2M concurrent processes pre-Meta.

4. Async/await coroutines (stackless)

A Future / Promise / Task is a state-machine struct. await suspends by saving state + yielding to the event loop. No stack — bytes per suspension.

LanguagePrimitiveRuntimeNotesLinked note
Pythonasync def, await, asyncioasyncio event loop (CPython); also Trio, Curio, AnyIOPEP 492 (3.5, 2015); TaskGroup PEP 654 (3.11, 2022); colored (must be async-aware)python
JavaScriptasync function, await, PromiseV8 microtask queue + libuvES2017; single-threaded event loop; Promise.all, Promise.allSettled, Promise.racejavascript
TypeScriptsame as JSinheritstyped Promises (Promise<T>); Effect-TS adds typed error channeltypescript
Rustasync fn, .await, Futureruntime-of-choice: Tokio (de facto), async-std (deprecated), smol, embassy (embedded)zero-cost — state machine compiled to enum; pollable; runtime-agnostic by language designrust
[[Languages/csharp|C#]]async, await, Task<T>, ValueTask<T>ThreadPool + I/O completion ports (Windows) / epoll (Linux)C# 5.0 (2012) — pioneered mainstream async/awaitcsharp
[[Languages/fsharp|F#]]async { return! ... } + computation expressions; also task { ... }inherits .NET TaskF# Async since 2007 (predates C# Task); task { } since F# 6.0 for Task<T> interopfsharp
Kotlinsuspend fun, launch, async/await, coroutinesDispatchers.Default, IO, Main, UnconfinedcoroutineScope + supervisorScope for structured concurrencykotlin
Swiftasync fn, await, Task, actorcooperative thread poolSwift 5.5 (2021); actor type isolates state; structured concurrency via TaskGroupswift
Dartasync, await, Future, StreamDart event loop + isolates (for parallelism)Dart 1.0+dart
Zigasync fn, suspend, resume; async removed in 0.11, may returnruntime-customizablecontroversial; async was pulled in 0.11 (2023) for redesignzig
Nimasync, await, Futureasyncdispatchdual: also has thread + spawnnim
OCamlLwt (Lwt.return, let%lwt), Async (Jane Street), Eio (OCaml 5 effects)variesOCaml 5 effects enable direct-style Eio (no monadic await)ocaml
ScalaFuture, for-comprehension; Cats Effect IO; ZIOruntime-of-choicefunctional-effect libs dominate new microservicesscala

Function coloring (Bob Nystrom 2015): in language with async/await, an async function can only be called from an async function (or with awkward bridging). “Red” (async) vs “Blue” (sync) functions infect the entire call graph. Swift, Kotlin, Rust, C#, Python all have this. Go + Erlang + Java virtual threads + Zig (proposed) avoid it.

5. Actors

A unit of computation that owns private state and processes a queue of messages. Concurrency is achieved by many actors running in parallel, communicating only through messages. No shared mutable state.

Language / libraryPrimitiveMailboxSupervisionNotesLinked note
Erlangspawn/1, ! (send), receiveunbounded FIFOOTP supervisor (gen_server, supervisor)the canonical model since 1986erlang
Elixirspawn, send, receive, GenServersamesameidentical to Erlangelixir
Gleamtyped actor via gleam_otpBEAMOTP supervisortyped message channelsgleam
ScalaAkka classic Actor / Akka Typedbounded/unboundedsupervisor strategyAkka now in BSL license (2022); Pekko (Apache fork 2022) is the OSS pathscala
JavaAkka / Pekko Java DSLas aboveas abovesame model on JVMjava
Ponyactor is first-class language entitybounded by defaultruntime-managed fault containmentreference capabilities prove no shared mutable state at compile timepony
[[Languages/csharp|C#]]Akka.NET; Orleans (Microsoft)variesgrain activationOrleans uses “virtual actors” with auto-activation/deactivationcsharp
Swiftactor keyword (Swift 5.5+)serialized executornonelanguage-level isolation enforced by compilerswift
RustActix actor lib; Bastion (less-maintained); Ractorvariesmanualactix-web is the largest userrust

Actor systems excel at fault isolation. The OTP supervisor model treats actor crashes as routine — a supervisor catches the crash + restarts a fresh actor. This is why WhatsApp/Discord/RabbitMQ/Riak built on Erlang/Elixir.

Akka was BSL-licensed in Sept 2022; Apache Pekko (Akka 2.6.x fork) is the OSS-licensed continuation. Pekko 1.0 GA shipped 2023-08.

6. CSP channels

Communicating Sequential Processes (Hoare 1978): independent processes communicate by sending values on channels. No shared state.

LanguageChannel APIBuffered / unbufferedNotesLinked note
Goch := make(chan int, 10); ch <- v; v := <-ch; selectboththe canonical formulation; CSP + goroutine + selectgo
Clojurecore.async; chan, >!, <!, go, alt!bothborrowed from Go; runs on JVM thread poolclojure
Ruststd::sync::mpsc, crossbeam::channel, tokio::sync::mpsc, flumebothcrossbeam: faster multi-producer multi-consumer; tokio for async; flume for performancerust
CrystalChannel(T)bothtyped; Crystal-fiber-friendlycrystal
Elixirmessage passing via mailboxes (no explicit channels; mailbox IS the channel)unboundedactor modelelixir
OCamlEvent (events.ml in stdlib); domainslib Chan for OCaml 5bothEio also provides streamsocaml
Erlangmailbox (process inbox)unboundedsame model as Elixirerlang
KotlinChannel<T>, produce, consume, selectbothpart of kotlinx.coroutines; also Flow for streamskotlin
SwiftAsyncStream, AsyncChannel (swift-async-algorithms)bothasync-algorithms package (2022+)swift
ScalaZIO Queue; Akka Streams Sourcebothfunctional effect libsscala

select (Go) / alt! (Clojure) / select! (Tokio) is the cross-cutting primitive — wait on multiple channels and act on the first ready.

7. STM — Software Transactional Memory

Composable lock-free synchronization: a block runs in a transaction; conflicts retry. Composes safely (you can compose two transactions into one); locks don’t.

LanguageAPINotesLinked note
HaskellSTM, TVar, atomically, retry, orElsethe canonical STM (Harris-Marlow 2005); enforced via type system — IO actions don’t compile inside atomicallyhaskell
Clojuredosync, ref, alter, commutesecond-best implementation; on JVMclojure
Scalascala-stm library; Akka STM (deprecated)OK but less usedscala
Ponyreference capabilities give similar guarantees at compile timeno runtime STM but the semantics are checked staticallypony
Rustparking_lot / crossbeam_epoch; STM crate exists but rarely usedRust prefers compile-time enforced sharingrust

STM stayed niche because its performance + memory overhead grow with contention, and Erlang/Go solved the same problem with isolation rather than transactions.

8. Reactive streams

Push-based dataflow: a stream emits events, downstream operators transform/filter/buffer, eventually consumed. Backpressure built-in.

LibraryLanguageSpecNotesLinked note
RxJavaJVMReactive Streams JVM specclassic Rx port; ReactiveX familyjava
RxJSJS/TSRxJava lineagenow rxjs@7+; common in Angularjavascript
RxSwiftSwiftRxJava lineagebeing displaced by Combine + async/awaitswift
CombineSwiftApple’s first-party reactiveiOS 13+; subsumed by AsyncSequence + structured concurrency in iOS 16+swift
Project ReactorJVMReactive Streams specSpring WebFlux backbonejava / kotlin
MutinyJVM (Quarkus)Reactive Streams specQuarkus frameworkjava
Akka Streams / Pekko StreamsJVMReactive Streams specactor + streamscala
FlowKotlincold; coroutine-basedkotlinx.coroutines.flow.Flow — Kotlin-native; replaces Rx for Androidkotlin
AsyncSequenceSwiftlanguage-leveliOS 15+swift
AsyncStreamC#language-levelC# 8+ via IAsyncEnumerable<T>csharp

The 2018+ trend: language-level async streams displace third-party reactive libraries. Kotlin Flow, Swift AsyncSequence, C# IAsyncEnumerable, Rust Stream all provide the 80% of Rx with cleaner integration. Rx libraries persist where you need rich operators (windowing, multicast, retry-with-backoff).

9. Event loops

The single-thread + queue model behind most async runtimes.

LoopLanguageBackendUsed byLinked note
libuvCepoll/kqueue/IOCP/io_uringNode.js, Julia, neovimjavascript / julia
TokioRustmio → epoll/kqueue/IOCP/io_uringhalf the async-Rust ecosystemrust
asyncioPythonselectors (kqueue/epoll/IOCP)stdlib (Python 3.4+)python
TrioPythonsimilarPython; pioneered nursery + structured concurrencypython
libevent / libevCepoll/kqueueolder C serversc
Boost.AsioC++epoll/kqueue/IOCP/io_uringdominant C++ I/Ocpp
io_uring (Linux 5.1+, 2019)kerneltrue async syscallsgradually replacing epoll for high-perf I/O on Linuxc
NettyJVMepoll/kqueueJava HTTP servers, gRPC, RSocketjava
Vert.xJVMNettyJava/Kotlin reactivejava
.NET ThreadPool + IOCP.NETWindows IOCP / epollC# async backbonecsharp
GCD (Grand Central Dispatch)ApplekqueueSwift / Obj-Cswift
Crystal Fiber schedulerCrystalepoll/kqueueCrystalcrystal
BEAM schedulerErlang/Elixirpoll + reduction-countingBEAMerlang
Go runtimeGonetpoller (epoll/kqueue/IOCP)Gogo

io_uring (Linux 5.1, 2019; stabilized 5.6+, mature 6.x 2023+) is the new high-performance Linux I/O — submission + completion queues in shared memory, no syscall per op. Tokio supports it as an optional backend; ScyllaDB Seastar uses it natively.

10. Thread pools + work-stealing

The bridge between many submitted tasks and few OS threads. Work-stealing balances load across pool threads.

Pool / libraryLanguageNotesLinked note
Java ForkJoinPoolJavathe canonical work-stealing pool; underpins parallelStream + CompletableFuture.async + virtual threadsjava
ThreadPoolExecutorJavanon-stealing; for bounded queue + named-thread scenariosjava
Go runtimeGoM:N + work-stealing across P (logical processor)go
Tokio multi-thread runtimeRustwork-stealing across worker threadsrust
Rust RayonRustdata-parallel work-stealing; par_iter()rust
.NET ThreadPoolC#hill-climbing heuristic + work-stealingcsharp
TBB (Intel oneAPI)C++task-based parallelism + parallel_forcpp
OpenMPC/C++/Fortrandirective-based parallel forc / cpp / fortran
Cilk PlusC/C++ (deprecated 2017+)the original work-stealing
Erlang BEAMErlangper-scheduler run queue with stealingerlang
OCaml 5 DomainslibOCamlwork-stealing pool atop domainsocaml
Julia threadsJuliawork-stealingjulia
Swift Concurrency cooperative poolSwiftper-core cooperative executorswift

11. Structured concurrency

A scope-rooted task tree: every spawned task is owned by some parent scope, the parent does not return until all children complete (or are cancelled). Nathaniel Smith’s Trio (Python, 2018) established the pattern; mainstream uptake has been broad.

LanguagePrimitiveNotesLinked note
PythonTrio nursery; stdlib asyncio.TaskGroup (3.11+)ExceptionGroup + except*python
KotlincoroutineScope { }, supervisorScope { }coroutines 1.0+ (2018)kotlin
SwiftwithTaskGroup, withThrowingTaskGroupSwift 5.5+swift
JavaStructuredTaskScope JEP 453 (preview J21; final J25 2025-09)first language-level Java structured concurrencyjava
[[Languages/c|C#]]not language-level; Task.WhenAll + CancellationTokenmanual pattern; no built-in scopecsharp
RustTokio JoinSet + select!; tokio::task::scope (unstable as of 1.85)runtime-level only; not enforcedrust
Goerrgroup.Group; no language-level scopede facto pattern via contextgo
ElixirTask.Supervisor + OTP supervisorOTP supervises beyond a single task treeelixir
OCamlEio Switch (OCaml 5 + effects)first-class via effectsocaml

12. Virtual threads (Java) and fibers (JVM/C++)

A radical 2018–2024 shift: rather than colored async/await, give every developer cheap green threads + blocking syscalls and let the runtime intercept blocking and yield.

LanguagePrimitiveStatusNotesLinked note
Javavirtual threads (JEP 444, J21)GA in J21 (2023-09)OS-thread-like API, runtime-scheduled atop carrier threads; ~kB stack; uncolored: blocking I/O auto-yieldsjava
Quasar (JVM)fibers (Parallel Universe, then deprecated 2018; replaced by Project Loom)supersededpredecessor of Loom virtual threadsjava
Project Loom (JVM)research projectshipped as J21 virtual threadsresearch → JEP 444 + JEP 453 + JEP 491java
Boost.FiberC++maturestackful coroutines on a thread poolcpp
Gogoroutinesmature since 2009the original virtual-thread systemgo
Skynet benchmarkcomparisonbenchmark shows 1M tasks: Erlang ~5s, Go ~6s, Java VT ~8s, Akka ~9s — all O(seconds) for 1M taskscomparison sitesn/a

Java virtual threads’ big idea: existing synchronized + blocking I/O code Just Works™ — without rewriting to async/await. As of Java 21 production deployments (Spring Boot 3.2+, Hibernate 7+, Helidon 4+) the migration story is “set -Dspring.threads.virtual.enabled=true and your servlet container scales to 100k concurrent”. J25 (2025-09) finalizes the structured-concurrency layer (JEP 491).

13. Per-language summary

LanguageNative primitiveIdiomatic for I/O concurrencyColoring?Linked note
PythonOS threads + GIL; asyncio coroutinesasyncio + TaskGroup; or threading (GIL-bound); 3.13t free-threadedyespython
JavaScriptevent loop + async/awaitasync/await + Promiseyesjavascript
TypeScriptinherits JSsame; Effect-TS for new microservicesyestypescript
JavaOS thread (legacy) → virtual thread (J21+)virtual threadsno (J21+)java
Cpthreadpthread + libuv/libev/io_uringn/a (no coroutines without lib)c
C++std::thread + std::jthread; coroutines (C++20)Boost.Asio + coroutines OR Boost.Fiberyescpp
[[Languages/csharp|C#]]Task + async/await + ThreadPoolasync/await + IOCPyescsharp
Gogoroutine + channel + selectgoroutines + channelsnogo
RustTokio async/await; thread for CPUTokio + structured spawnyesrust
Swiftactor + async/await + Taskasync/await + actoryesswift
Kotlincoroutines + Flow + Channelcoroutines + structured scopeyes (suspend)kotlin
RubyThread (GIL until 3.0) + Ractor (3.0+) + FiberAsync (gem) for I/O, Ractor for parallelismmixedruby
PHPprocess-per-request (FPM) + ReactPHP / Amp / SwooleSwoole / Amp for asyncyesphp
ScalaFuture + Akka/Pekko + Cats Effect + ZIOCats Effect 3 or ZIO 2variesscala
DartFuture + Stream + Isolateasync/await + Streamyesdart
ElixirBEAM processspawn + GenServer + Tasknoelixir
Haskellgreen thread + STM + async libforkIO + STM + async packageyes (IO)haskell
ClojureJVM thread + core.async + future + agentcore.async or futuresmixedclojure
[[Languages/fsharp|F#]]Task + async + agentasync { } or task { }yesfsharp
Luacoroutine (single-threaded) + LuaJIT + luaproccoroutine + lib (Copas, OpenResty cosocket)yeslua
Rfuture + furrr; parallelfuture package + furrr; mostly batchn/ar
JuliaThreads.@spawn + Task + Channel@async + fetch + Channelpartialjulia
Zigthread + async (removed 0.11 → being redesigned)thread; async TBDTBDzig
Nimthread + async + spawnasyncdispatchyesnim
CrystalFiber + ChannelFiber + Channel CSPnocrystal
OCamlOCaml 5 domain + Eio (effects)Lwt (monad) or Eio (effects, OCaml 5+)OCaml 5 effects: noocaml
Perlthread (discouraged) + fork + AnyEventfork + AnyEvent / Mojoliciousmixedperl
ErlangBEAM process + mailboxspawn + receive + supervisornoerlang
Racketthread + place + futurethread + placen/aracket
Common Lispbordeaux-threadsimpl-specific (SBCL: native threads)n/acommon-lisp
Schemeimpl-specific; SRFI-18 threadsimpl-specificn/ascheme
FortranOpenMP + coarrayOpenMP + MPIn/afortran
COBOLclassical: single-threaded; modern: CICS multi-regionCICS + IMSn/acobol
Adatask (first-class) + protectedtask + rendezvousn/aada
Pascalclassical: none; Delphi: Thread + TParallel.ForThread / TTaskn/apascal
Prologimpl-specific (SWI: threads); engines in somethread_createn/aprolog
Tclevent loop + after + thread packageevent loopn/atcl
Groovyinherits JVMGPars + ParallelGroupyesgroovy
Bash& (fork) + wait + xargs -Pfork + paralleln/abash
PowerShellJob + Workflow + ForEach -Parallel (PS 7+)-Parallel + ThreadJobn/apowershell
SQLengine-managed (parallel query)declarativen/asql
Vgo-style spawn + channelgo { … }nov
Odinthread; sync packagethread + libn/aodin
Rocplatform-definedplatform-supplied effectsplatform-deproc
GleamBEAM processOTP via gleam_otpnogleam
Ponyactor (first-class)actor + reference capsnopony
Idrisimpl-specific; effects + handlersresearch-stagen/aidris
LeanTask + IO monadfunctional; not heavy asyncyeslean
Coq (Rocq)n/a (proof asst)OCaml runtimen/acoq
Agdan/aGHC runtimen/aagda
SmalltalkProcess (green thread)Process + Semaphoren/asmalltalk
MATLABparfeval + Parallel Computing ToolboxPCT + parforn/amatlab
Mojoparallelize + SIMD + GPUparallelize / vectorize / GPUTBDmojo

14. The convergence and divergence — 2020–2026

Two opposing trends fight in the language design space:

(A) Uncolored / Loom-style — virtual threads + blocking I/O. Pioneered by Erlang (1986) and Go (2009); brought to mainstream JVM by Project Loom (J21, 2023). The pitch: existing synchronous code scales to 100k+ concurrent without rewriting. Maintenance + onboarding cost much lower.

(B) Colored async/await — explicit async/await. Strong in Rust (zero-cost futures), C# (Task), Swift (Task), Kotlin (suspend), Python (asyncio), JS. The pitch: visible suspension points + zero-cost state machines + cancellation semantics.

The 2024–2026 movement is convergence on the middle:

  • Java got virtual threads (uncolored) AND StructuredTaskScope (structured concurrency).
  • Kotlin coroutines are colored but structured.
  • Swift 6 added typed throws + actor isolation.
  • Python 3.13t (free-threaded build) + asyncio TaskGroup gives both true parallelism + structured async.
  • OCaml 5 effects are uncolored async — let x = perform Read; ... works without async-coloring the function signature.
  • io_uring (Linux 2019, mature 2024) shifts kernel I/O closer to truly async.

The research-language frontier is algebraic effects (Koka, Eff, Effekt, OCaml 5) — uncolored async without a runtime, just typed effects + handlers. Eio is the production face of this.

Adjacent

When to pick what

Workload?
├─ Mostly CPU-bound parallel compute
│   ├─ Data parallel → OpenMP / Rust Rayon / Julia Threads / MATLAB parfor / Mojo parallelize / Fortran coarray
│   ├─ Task parallel → ForkJoinPool / Rayon / TBB / OCaml Domainslib
│   └─ GPU → CUDA / Mojo / Triton (Python) / WebGPU
├─ Mostly I/O-bound, < 10⁴ connections
│   ├─ Any modern language's default is fine; use OS threads if convenient
│   └─ Erlang/Elixir/Go shine even at this scale due to simplicity
├─ I/O-bound, 10⁴–10⁶ connections
│   ├─ Go goroutines (smallest learning curve)
│   ├─ Erlang/Elixir BEAM (fault-tolerant)
│   ├─ Java virtual threads J21+ (existing Java codebase)
│   ├─ Rust Tokio (max throughput + safety)
│   └─ Node.js (JS skills, single-thread + cluster)
├─ Real-time / hard latency
│   ├─ Embedded → Rust no_std + embassy executor; C with RTOS (FreeRTOS, Zephyr)
│   ├─ Audio / video → C++ with lock-free + Boost.Asio; Rust real-time
│   └─ Trading → C++ + custom kernel-bypass (DPDK, Solarflare); Rust + io_uring
├─ Distributed / fault-tolerant millions of conns
│   └─ Erlang/Elixir OTP supervisor; or Akka/Pekko (JVM); or Orleans (.NET)
├─ Browser / single event loop
│   └─ async/await + Promise + Web Workers for parallelism
├─ Reactive UI / dataflow
│   ├─ Mobile → Kotlin Flow / Swift AsyncSequence / Combine
│   ├─ Web → RxJS or signals (SolidJS, Svelte 5, Vue 3 reactivity)
│   └─ Backend dataflow → Akka Streams / Pekko Streams / Project Reactor / Mutiny
├─ Data pipeline / ETL
│   ├─ Stream processing → Kafka Streams (JVM), Flink, Beam, Spark Structured Streaming
│   ├─ Batch → Apache Airflow / Prefect / Dagster + multiprocessing
│   └─ Functional effect-typed → ZIO / Cats Effect
├─ Composable transactions
│   └─ Haskell STM (canonical); Clojure dosync; Pony reference caps
├─ Need uncolored code today
│   ├─ Go, Erlang, Elixir, Gleam, Crystal — pure uncolored
│   └─ Java virtual threads (J21+) — uncolored for JVM
└─ Embedded / no_std / heap-free
     └─ Rust embassy executor; C with RTOS; Zig (TBD async); Ada Ravenscar profile

The single biggest practical lesson 2018–2026: uncolored beats colored at scale-of-developer-time. The companies that have moved fastest at scale — Discord (Elixir), WhatsApp (Erlang), Cloudflare (Go heavy), Adobe Stock (Go), Stripe (Ruby + Go for hot paths), Twitter (Scala + JVM virtual threads in progress), and the entire Java enterprise stack post-Loom — all bet on uncolored. Async/await colored stays the right choice for library code (Rust, C#, Python), single-threaded event loops (JS, Node), and maximum throughput (Tokio, ZIO, Cats Effect). For everything else, prefer the model that lets you write straightforward sequential code that scales.