当前位置:首页>java>重学Zig之旅 | 第11天:异步编程(async/await)

重学Zig之旅 | 第11天:异步编程(async/await)

  • 2026-10-30 11:01:10
重学Zig之旅 | 第11天:异步编程(async/await)

大家好!重学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):

  • async fn 定义一个可以暂停的函数
  • await 消费一个异步帧,恢复执行
  • suspend 显式暂停点
  • resume 从外部恢复协程

核心思想:

  • 异步函数返回一个 frame(栈帧指针),而不是立即执行
  • 需要一个 event loop 来驱动所有协程(标准库提供简单实现)
  • 无线程、无绿线程,只有单线程事件驱动(也可结合多线程)

优势:

  • 零分配(除非你手动 alloc 帧)
  • 确定性行为,无数据竞争(单线程默认)
  • 可预测的栈使用

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,不立即执行函数体
  • suspend {} 块是暂停点,进入时立即暂停
  • 要恢复,需要从外部 resume 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,可嵌入裸机
  • 单线程优先:避免数据竞争,易推理
  • 与 comptime 结合:可生成异步状态机

Zig 的 async 适合高性能服务器、嵌入式、游戏等场景。目前生态(如 tigerbeetle、bun)已大量使用。


今日练习

  1. 实现一个简单的“并发”打印数字:启动 5 个异步任务,每个任务暂停不同时间后打印自身 ID。
  2. 改写一个同步的递归斐波那契为异步版本(用 suspend 模拟“等待子任务”),体会帧的使用。
  3. 研究标准库 std.net.StreamServer 的异步 accept 示例(提示:用 async accept)。

把代码贴评论区交流!有问题随时问~


明天第12天,我们深入 指针、切片与手动内存管理,彻底掌握 Zig 的内存安全哲学,敬请期待!

如果今天的异步内容让你兴奋,记得点赞、在看、转发!你的支持让我更有动力继续更新!

重学 Zig 之旅,第11天完成!坚持就是胜利,一起加油!

最新文章

随机文章