Show HN: Runloom – 面向 Python 自由线程(free-threaded)的 Go 风格协程
Show HN: Runloom – Go-style coroutines for Python free-threaded

原始链接: https://github.com/robertsdotpm/runloom

**Runloom** 是一个高性能库,为 Python 引入了 Go 风格的有栈协程(stackful coroutines)。通过利用手写的汇编上下文切换、基于 C 语言的工作窃取调度器以及高效的 `netpoll` 实现,它使开发者能够编写跨所有 CPU 核心并发运行的阻塞式代码。 主要特性包括: * **易用性**:支持 `monkey.patch()`,无需 `async/await` 即可将标准阻塞式 I/O(套接字、时间等)转换为非阻塞的协作式代码。 * **高性能**:专为自由线程(free-threaded)CPython (3.13t/3.14t) 设计,吞吐量和生成速率可与 Go 媲美。 * **架构**:实现了 M:N 调度器,将任务分配到多个操作系统线程中,有效绕过了传统单线程 Python 并发的限制。 * **兼容性**:为旧代码提供 `asyncio` 桥接,并可通过预编译的 wheel 包在主流操作系统和架构上使用。 尽管由于 CPython 执行帧的原因,Runloom 每个纤程的内存开销约为 Go 的 3.3 倍,但它为 Python 实现多核并行提供了一种强大的方式。它并不会加速单个 Python 字节码的执行,但能够让单个进程的阻塞式 Python 逻辑扩展到所有可用的硬件核心上。

Hacker News 最新 | 过往 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 Show HN: Runloom – 为 Python 自由线程版(free-threaded)提供 Go 风格的协程 (github.com/robertsdotpm) 7 分,由 Uptrenda 发布于 1 小时前 | 隐藏 | 过往 | 收藏 | 1 条评论 ksdme9 16 分钟前 [–] 这与 gevent 相比如何? 回复 准则 | 常见问题 | 列表 | API | 安全 | 法律 | 申请加入 YC | 联系 搜索:
相关文章

原文

Go-style stackful coroutines for Python. Write blocking code — fiber(fn), plain recv/send, no async/await — and run a million of them across every core in one process. Hand-rolled asm context switch + C work-stealing scheduler + netpoll, built for free-threaded Python 3.14t (GIL off).

import threading, runloom
from urllib.request import urlopen
runloom.monkey.patch()

def crawl(url):
    # urlopen() looks blocking -- but monkey.patch() parks the goroutine on the
    # socket instead of the OS thread, so all 64 fetches overlap on real cores.
    body = urlopen(url, timeout=10).read()
    print(threading.get_native_id(), len(body))

def main():
    for _ in range(64):
        runloom.fiber(crawl, "http://example.com")

runloom.run(8, main)   # 8 hub threads -> real cores on 3.14t (GIL off)

Same box (64c, free-threaded CPython 3.13t), 8 hubs / GOMAXPROCS=8, warm steady-state. Go ≈ 2.1 M spawn/s here.

metric runloom Go verdict
spawn — pure C (c_entry) 2.29 M/s 2.10 M/s beats Go
spawn — Python (runloom.fiber) 1.35 M/s 2.10 M/s 0.65×
context switch ~75 ns yield · ~560 ns chan RT ~50 ns Gosched ~parity
conn/s — churn (new conn per req) ~75–78 k/s ~75–78 k/s parity
req/s — keep-alive echo, Python handler 596 k/s 603 k/s 0.99× — parity (C handler beats Go)
memory — empty parked fiber 8.8 KB 2.7 KB 3.3× (the one real gap)

The short story: on spawn, scheduling, and throughput, runloom trades blows with Go and beats it on raw spawn — a stackful coroutine runtime on CPython matching a compiled language even with a Python handler (596 k vs 603 k req/s at saturation; a C handler beats Go). The one honest gap left is memory: a suspended fiber carries a CPython eval frame, ~3.3× Go's per-fiber RSS. Full cross-runtime numbers + cold spawn-vs-N curves: benchmark report · perf summary.

runloom.optimize("throughput")   # runloom.fiber -> max spawn rate (fiber_fast)
runloom.optimize("memory")       # runloom.fiber -> small right-sized stacks (default)
import runloom      # scheduler + channels, plus monkey/time/context/sync/aio

Prebuilt wheels (no compiler needed) for CPython 3.11–3.14 on Linux (x86_64/aarch64), macOS (arm64/x86_64), Windows (AMD64); source build elsewhere. No runtime dependencies.

  • Hand-rolled asm context switch (x86_64 SysV, aarch64) — ~80 ns/swap, no syscall; Windows Fibers / POSIX ucontext fallback.
  • M:N work-stealing scheduler (3.13t) — Chase-Lev deque per hub, per-hub MPSC submission, woken goroutines routed back to their origin hub.
  • Per-goroutine PyThreadState snapshot — cframe, datastack, exc_info, contextvars, recursion; a million yielded goroutines share their hub threads with no frame-chain cliff.
  • netpoll — epoll / kqueue / IOCP / WSAPoll / select; goroutines park transparently on fd readiness, lost-wake-free 3-state park-commit.
  • Go-style channelsChan(capacity), select, for v in ch.
  • Stall isolation + recovery — one unanticipated blocking call stalls only its hub, and the runtime detects + recovers it (default on, 3.13t).
  • monkey.patch() makes blocking stdlib (socket, time, threading, …) cooperative, so existing blocking code runs unchanged.

Already have async def code? The runloom.aio bridge runs it on the single-threaded scheduler (runloom.aio.run(main())asyncio.run) — a zero-rewrite port path, not a multi-core speedup (use the sync API with run(n>1, main) for that).

  • The multi-core win needs free-threaded CPython 3.13t (3.11+ for the frame snapshot at all). On a GIL build runloom still runs — cheap spawn, the goroutine model, netpoll — but single-core like asyncio.
  • runloom doesn't make Python faster per core. CPython's ~80 k pure-Python ops/s/core is a constant it can't raise; it lets one process hit that on every core at once with a blocking model. The scheduler itself is Go-class.
  • Higher memory per goroutine than Go (~3.3× for an empty fiber — the CPython eval frame; a C handler closes most of it).
  • Preemption fires only at Python bytecode boundaries — a goroutine inside a tight pure-C call (e.g. numpy) holds its hub until it returns (same as Go + cgo).
  • Linux x86_64 / 3.13t is the primary, heavily-validated target (2 M-conn runs, fuzzing, sanitizers, formal models); other backends are maintained in-step but less deeply exercised.
OS / arch switch netpoll tested
Linux x86_64 fcontext-asm epoll yes — hw, 3.11 / 3.12 / 3.13t / 3.14t (primary)
Linux aarch64 fcontext-asm epoll qemu
macOS x86_64 / arm64 fcontext-asm kqueue hw, 3.14t
FreeBSD / GhostBSD fcontext-asm kqueue hw, 3.12
Windows 10/11 / Server 2022 Fibers IOCP→WSAPoll→select hw, 3.14t
Solaris / Android / other BSD ucontext / asm select / epoll / kqueue review

Full guide in docs/: Quickstart · Asyncio bridge · Sync API · Channels · M:N parallelism · Cookbook · API reference

Dir Contents
src/runloom_c/ C extension: scheduler, channels, netpoll, asm backends, M:N hubs, stall recovery
src/runloom/ Python layers: aio, sync, monkey, time, runtime
tests/ · examples/ · benchmark/ · docs/ tests · runnable examples · benchmarks + perf harness · docs

Build from source (contributors): pip install -e . from a clone (needs a C compiler; scripts/install.sh / scripts\install.bat bootstrap one). To hack on runloom against free-threaded CPython, use a 3.13t interpreter.

联系我们 contact @ memedata.com