文章背景与核心概要
本文探讨了 Zig 语言中实现并发的新 Io 接口的其中一个实现——std.Io.Threaded。尽管它看似是一个“枯燥”的基于线程的实现,但其设计极其优雅。它解决了一个系统编程中长期存在的难题——如何可靠地取消阻塞的系统调用(syscall),并且其稳健程度超出了以往的认知。
文章指出,并发的关键在于“取消”(cancellation),而传统的基于线程的模型在处理陷入内核系统调用的线程时往往束手无策。Zig 的 Io.Threaded 在 POSIX 系统上巧妙地运用了基于信号(signal)的协议(在 Windows 上则使用 NtCancelSynchronousIoFile),配合语言自身的错误处理模型,使得在不引入 io_uring 等现代异步运行时的高开销和复杂性的前提下,安全、可靠地取消阻塞系统调用成为可能。
Zig's Io.Threaded is Neat
August 6, 2026
std.Io.Threaded 是 Zig 用于实现并发的新 Io 接口的实现之一。虽然它看起来像是一个“枯燥的”基于线程的实现,但它却异常优雅。它以一种比以往认为的更稳健的方式,解决了系统编程中长期存在的挑战——可靠地取消阻塞系统调用。
std.Io.Threaded is one of the implementations of Zig’s new
Iointerface that enables concurrency. While it might seem like a "boring" thread-based implementation, it is remarkably elegant. It solves a long-standing challenge in systems programming—reliable cancellation of blocking syscalls—in a way that is more robust than previously thought possible.
Summary
本文探讨了 Zig 的 std.Io.Threaded,这是一个利用标准操作系统线程和阻塞系统调用提供并发能力的实现。与在取消操作上举步维艰的传统线程模型不同,Zig 在 POSIX 系统上使用了一种巧妙的基于信号的协议(在 Windows 上使用 NtCancelSynchronousIoFile),从而允许线程被安全地中断。通过将其集成到语言的错误处理模型中,Zig 提供了一种在没有现代异步运行时(如 io_uring)所带来的开销或复杂性的情况下实现可靠取消的方法。
Summary
The article explores Zig's
std.Io.Threaded, an implementation that provides concurrency using standard OS threads and blocking syscalls. Unlike traditional threading models that struggle with cancellation, Zig uses a clever signal-based protocol on POSIX (andNtCancelSynchronousIoFileon Windows) to allow threads to be interrupted safely. By integrating this into the language's error-handling model, Zig provides a way to achieve reliable cancellation without the overhead or complexity of modern asynchronous runtimes likeio_uring.
Concurrency vs. Parallelism
借鉴 @tedinski 的观点,我们可以区分这两者: * 并发(Concurrency) 关乎处理异步、非确定的事件。 * 并行(Parallelism) 关乎利用硬件资源同时执行任务。
并行通常是确定性/声明式的(例如 Rust 中的 Rayon),由平台负责管理任务切分。然而,并发则不可避免地涉及取消操作。当两个异步任务在运行时,其中一个可能会因为变得冗余或无法完成而需要主动取消另一个。
Concurrency vs. Parallelism
Drawing from @tedinski, we can distinguish the two: * Concurrency is about handling asynchronous, nondeterministic events. * Parallelism is about using hardware resources to perform tasks simultaneously.
Parallelism is typically deterministic/declarative (e.g., Rayon in Rust), where the platform manages partitioning. Concurrency, however, invariably involves cancellation. When two asynchronous tasks run, one may need to actively cancel the other because it has become redundant or impossible to complete.
Just Use Threads
“直接使用线程”(Just Use Threads)的主要问题在于:当它们阻塞在内核系统调用内部时,无法将其取消。虽然你可以在用户空间的循环中轻松检查取消标志,但一旦遇到诸如 read(fd, buffer) 这样的系统调用,你就会陷入困境。Zig 的 Io.Threaded 通过允许可靠地中断标准操作系统线程,完美解决了这一问题。
Just Use Threads
The primary issue with "just using threads" is the inability to cancel them when they are blocked inside a kernel syscall. While you can easily check for cancellation in a user-space loop, you are stuck once you hit a syscall like
read(fd, buffer). Zig’sIo.Threadedsolves this by allowing standard OS threads to be interrupted reliably.
SIGIO
在 POSIX 系统上,Zig 使用了一种“饱受诟病”(cursed)但极其有效的基于信号的协议:
1. 取消线程在共享内存中设置一个标志。
2. 它在循环中向目标线程发送信号。
3. 目标线程从系统调用中收到 EINTR 返回后,会检查该标志。
4. 如果标志已设置,线程确认取消并开始栈展开(unwinding);否则,它将重试该系统调用。
在 Windows 上,这通过 NtCancelSynchronousIoFile 得到了更直接的处理。在 Zig 中,这种取消操作被具象化为 error.Canceled,从而将取消作为一种特定类型的错误载荷来处理。
SIGIO
On POSIX, Zig uses a "cursed" but effective signal-based protocol: 1. The canceling thread sets a flag in shared memory. 2. It signals the target thread in a loop. 3. The target thread, upon receiving
EINTRfrom a syscall, checks the flag. 4. If the flag is set, the thread acknowledges the cancellation and begins unwinding; otherwise, it retries the syscall.On Windows, this is handled more directly via
NtCancelSynchronousIoFile. In Zig, this cancellation is materialized aserror.Canceled, treating cancellation as a specific type of error payload.
Prior Art
- Java: 存在线程中断机制,但不支持中断系统调用,这导致 IO 操作实际上是无法被中断的。
- pthread_cancel: 存在这种机制,但它缺乏与语言级特性(如
try和defer)的集成,这使得资源清理变得十分困难。此外,它还饱受线程创建高开销的困扰。 - Zig 的方法: Zig 在接口层面上将“可能并发运行”(may run concurrently)与“必须并发运行”(must run concurrently)区分开来。通过使用线程池以及精确的命名(
io.async对比io.concurrent),Zig 为并发提供了一种清晰、高性能且安全的模型,避免了内核、运行时和语言之间尴尬的“中间地带”。
Prior Art
- Java: Thread interruption exists but does not support interrupting syscalls, making IO operations effectively non-interruptible.
- pthread_cancel: This mechanism exists but lacks integration with language-level features like
tryanddefer, making cleanup difficult. It also suffers from the high cost of thread creation.- Zig's Approach: Zig separates "may run concurrently" from "must run concurrently" at the interface level. By using a thread pool and precise naming (
io.asyncvsio.concurrent), Zig provides a clear, performant, and safe model for concurrency that avoids the "twilight zone" between kernel, runtime, and language.