大家好!重学Zig之旅打卡第11天,我们已经走过了函数、comptime、结构体、枚举与联合体这些核心基础。今天我们进入一个既酷炫又实用的领域——异步编程(async/await)。
Zig 的异步模型是语言级原生支持的协程(coroutine)机制,轻量、高效、无运行时开销,完全符合 Zig “无隐藏控制流、无隐藏分配”的哲学。不同于 Go 的 goroutine 或 Rust 的 async(需要复杂 runtime),Zig 的 async 更接近“彩色函数”(colored functions),但通过显式 event loop 管理,保持了极致的控制力。
注意:Zig 的 async 特性在 0.11+ 版本已趋于稳定,但标准库的异步 I/O 支持仍在演进中。今天我们重点掌握语言机制,实战部分用简单例子演示。
准备好探索 Zig 的并发世界了吗?出发!
1. Zig 异步模型概览
Zig 的异步基于 协程帧(frame):
核心思想:
- 异步函数返回一个 frame(栈帧指针),而不是立即执行
- 需要一个 event loop 来驱动所有协程(标准库提供简单实现)
- 无线程、无绿线程,只有单线程事件驱动(也可结合多线程)
优势:
2. 基础示例:Hello Async
先来一个最简单的异步函数:
const std = @import("std");
pub fn main() !void {
// 创建一个异步帧
var frame = async myAsyncFunction();
// 用 nosuspend 等待完成(简单场景)
await frame;
}
fn myAsyncFunction() void {
std.debug.print("异步开始\n", .{});
// 暂停点(模拟 I/O)
suspend {
std.debug.print("暂停中...\n", .{});
// 这里可以注册 wakeup
}
std.debug.print("异步恢复,继续执行\n", .{});
}
运行输出:
异步开始
暂停中...
异步恢复,继续执行
解释:
async myAsyncFunction() 返回一个 frame,不立即执行函数体
如何真正恢复?
简单方式:用 resume 在 suspend 块里自调度(演示用):
fn myAsyncFunction() void {
std.debug.print("开始\n", .{});
suspend {
// 在 suspend 块里可以安排后续 resume
@setCold(true); // 提示冷路径
resume @frame(); // 立即恢复当前帧(演示用)
}
std.debug.print("恢复\n", .{});
}
更实际:用 event loop 驱动。
3. Event Loop 与真实异步 I/O
Zig 标准库提供 std.event.Loop(实验性,但可用)。
一个完整例子:多个协程并发执行(模拟定时任务)。
const std = @import("std");
var loop: std.event.Loop = undefined;
pub fn main() !void {
loop = std.event.Loop.init(.{});
defer loop.deinit();
// 启动多个异步任务
var f1 = async task(1, 300);
var f2 = async task(2, 200);
var f3 = async task(3, 100);
// 驱动 event loop 直到所有完成
try loop.run();
// 等待所有任务(可选)
await f1;
await f2;
await f3;
}
fn task(id: u8, delay_ms: u64) void {
std.debug.print("任务 {} 开始\n", .{id});
// 模拟异步等待(用 timer)
suspend {
var timer = std.time.Timer.start() catch unreachable;
const target = timer.read() + delay_ms * 1_000_000;
loop.addTimer(target, @frame());
}
std.debug.print("任务 {} 完成\n", .{id});
}
(注:实际代码需扩展 loop.addTimer 实现,标准库有示例。这里简化演示概念)
关键点:
suspend 后注册 wakeup(timer、I/O 等)- event loop 轮询事件,事件就绪时
resume 对应帧
4. 高级技巧
帧分配与管理
默认 async 用调用者栈,但大协程需手动分配:
var my_frame = try allocator.create(@frameType(myAsync));
my_frame.* = async myAsync();
defer allocator.destroy(my_frame);
noasync 关键字
在同步上下文中强制同步调用异步函数:
nosuspend await frame; // 必须当前无 pending suspend
或:
const result = nosuspend async myAsync(); // 直接同步执行
错误处理
异步函数也可返回错误:
async fn maybeFail() !void {
suspend {}
return error.Failed;
}
var frame = async maybeFail() catch |err| {
std.debug.print("捕获错误: {}\n", .{err});
return;
};
5. 小结:Zig async 的哲学
- 显式控制:suspend/resume 让你精确知道暂停点
- 轻量高效:无 runtime overhead,可嵌入裸机
Zig 的 async 适合高性能服务器、嵌入式、游戏等场景。目前生态(如 tigerbeetle、bun)已大量使用。
今日练习
- 实现一个简单的“并发”打印数字:启动 5 个异步任务,每个任务暂停不同时间后打印自身 ID。
- 改写一个同步的递归斐波那契为异步版本(用 suspend 模拟“等待子任务”),体会帧的使用。
- 研究标准库
std.net.StreamServer 的异步 accept 示例(提示:用 async accept)。
把代码贴评论区交流!有问题随时问~
明天第12天,我们深入 指针、切片与手动内存管理,彻底掌握 Zig 的内存安全哲学,敬请期待!
如果今天的异步内容让你兴奋,记得点赞、在看、转发!你的支持让我更有动力继续更新!
重学 Zig 之旅,第11天完成!坚持就是胜利,一起加油!