Async I/O in Zig 0.16, today
Async I/O in Zig 0.16, today
Zig 0.16 中的异步 I/O:现状与实践
Zig 0.16 shipped last month with std.Io, a cross-platform interface for I/O and concurrency. This is a big step for the ecosystem. Libraries can now be written against a standard I/O abstraction, independent of the runtime, and application developers can plug in whatever implementation they want.
Zig 0.16 于上个月发布,引入了 std.Io,这是一个用于 I/O 和并发的跨平台接口。这对整个生态系统来说是一大进步。现在,库可以基于标准的 I/O 抽象进行编写,而无需依赖特定的运行时,应用程序开发者可以根据需要插入任何实现。
The only usable implementation shipped with 0.16 is std.Io.Threaded, which uses a thread pool. When you spawn concurrent tasks, it creates OS threads to run them. Let’s see how it works with a simple example:
0.16 版本中唯一可用的实现是 std.Io.Threaded,它使用线程池。当你启动并发任务时,它会创建操作系统线程来运行它们。让我们通过一个简单的例子来看看它是如何工作的:
const std = @import("std");
const num_tasks = 10_000;
fn task(io: std.Io) std.Io.Cancelable!void {
try io.sleep(.fromSeconds(10), .awake);
}
pub fn main(init: std.process.Init) !void {
var group: std.Io.Group = .init;
for (0..num_tasks) |_| {
try group.concurrent(init.io, task, .{init.io});
}
try group.await(init.io);
}
This spawns 10,000 concurrent tasks, each sleeping for 10 seconds. On my machine, it completes in about 20 seconds: 这段代码启动了 10,000 个并发任务,每个任务休眠 10 秒。在我的机器上,它大约需要 20 秒完成:
$ time ./std_demo
real 0m20.158s
user 0m2.258s
sys 0m10.098s
The overhead comes from spawning OS threads. If you try increasing this to 50,000 tasks, it will likely fail on most systems due to thread limits (ulimit -u on Linux). This isn’t just an arbitrary benchmark. Asynchronous I/O exists to solve a real problem: network servers with many connected clients. You don’t want to spawn an OS thread for every client connection. That’s why we have event loops, coroutines, and async I/O.
开销主要来自创建操作系统线程。如果你尝试将任务数增加到 50,000,由于线程限制(Linux 下的 ulimit -u),大多数系统很可能会失败。这不仅仅是一个随意的基准测试。异步 I/O 的存在是为了解决一个实际问题:拥有大量连接客户端的网络服务器。你肯定不希望为每个客户端连接都创建一个操作系统线程。这就是为什么我们需要事件循环、协程和异步 I/O。
There is std.Io.Evented in the standard library, which is meant to use io_uring on Linux and kqueue on BSD/macOS. It’s still a work in progress though, missing many functions and doesn’t currently compile.
标准库中虽然有 std.Io.Evented,旨在 Linux 上使用 io_uring,在 BSD/macOS 上使用 kqueue。但它目前仍在开发中,缺失许多功能,且暂时无法编译。
I’ve written about zio before, and I’ve just released version 0.11 with a full std.Io implementation. It uses stackful coroutines and asynchronous OS-level I/O APIs (io_uring or epoll on Linux, kqueue on BSD/macOS, IOCP on Windows). Here’s the same example using zio:
我之前写过关于 zio 的文章,刚刚发布的 0.11 版本提供了完整的 std.Io 实现。它使用有栈协程和异步操作系统级 I/O API(Linux 上的 io_uring 或 epoll,BSD/macOS 上的 kqueue,Windows 上的 IOCP)。以下是使用 zio 实现的相同示例:
const std = @import("std");
const zio = @import("zio");
const num_tasks = 10_000;
fn task(io: std.Io) std.Io.Cancelable!void {
try io.sleep(.fromSeconds(10), .awake);
}
pub fn main(init: std.process.Init) !void {
const rt = try zio.Runtime.init(init.gpa, .{});
defer rt.deinit();
const io = rt.io();
var group: std.Io.Group = .init;
for (0..num_tasks) |_| {
try group.concurrent(io, task, .{io});
}
try group.await(io);
}
The code is almost identical. You just initialize a zio runtime and use its io() method to get the std.Io interface. With zio, the same 10,000 tasks complete in about 10 seconds:
代码几乎完全相同。你只需要初始化一个 zio 运行时,并使用它的 io() 方法获取 std.Io 接口即可。使用 zio,同样的 10,000 个任务大约只需 10 秒即可完成:
$ time ./zio_demo
real 0m10.606s
user 0m3.136s
sys 0m7.126s
That’s the expected time, since all tasks run truly concurrently. You can increase this to 50,000 or more tasks and it will continue to work, limited only by available memory. You can use this io instance for anything you’d use std.Io.Threaded for. To write an HTTP server with std.http.Server, for example, just pass zio’s io and it will work the same way.
这是预期的耗时,因为所有任务都在真正地并发运行。你可以将任务数增加到 50,000 或更多,它依然能正常工作,仅受限于可用内存。你可以将此 io 实例用于任何原本使用 std.Io.Threaded 的场景。例如,要使用 std.http.Server 编写 HTTP 服务器,只需传入 zio 的 io,它就能以同样的方式工作。
If you want to use async I/O in Zig 0.16 with the standard APIs, you don’t need to wait for std.Io.Evented to be ready. Zio’s implementation is still new, so if you hit any problems, please reach out on GitHub and I’ll be happy to help.
如果你想在 Zig 0.16 中使用标准 API 进行异步 I/O,无需等待 std.Io.Evented 就绪。Zio 的实现尚属新事物,如果你遇到任何问题,请在 GitHub 上联系我,我很乐意提供帮助。