Ziglings 笔记 104: 多线程 (Multithreading)

火力全开:多线程

在 Ziglings 的第 104 个练习中,我们终于触及了榨干 CPU 性能的关键技术——多线程 (Multithreading)

async 这种轻量级的协作式多任务不同,std.Thread 对应的是操作系统级的内核线程。这意味着你的代码可以真正地同时跑在多个 CPU 核心上,实现物理上的并行。

挑战:三个火枪手

我们需要启动三个线程,让它们并行执行 thread_function。 主线程需要等待它们全部完成后才能退出,否则它们可能会被腰斩。

解决方案

我们需要正确地启动线程,并为每个线程注册清理操作(join)。

const std = @import("std");

pub fn main() !void {
    std.debug.print("Starting work...\n", .{});

    // 创建一个作用域,用于管理线程生命周期
    {
        // 1. 启动线程 1
        // 参数传递方式:.{1} (作为元组)
        const handle1 = try std.Thread.spawn(.{}, thread_function, .{1});
        
        // 确保最终等待线程结束并释放资源
        defer handle1.join();

        // 2. 启动线程 2
        // 修复点:确保参数正确传入
        const handle2 = try std.Thread.spawn(.{}, thread_function, .{2});
        defer handle2.join();

        // 3. 启动线程 3
        const handle3 = try std.Thread.spawn(.{}, thread_function, .{3});
        
        // 修复点:别忘了 join 第三个线程!
        // 如果不 join,主线程结束时该线程可能还在运行,导致未定义行为或崩溃。
        defer handle3.join();

        // 在主线程中做点别的事...
        // 此时,thread 1, 2, 3 和主线程正在同时运行(如果 CPU 核心够多)
        std.time.sleep(1 * std.time.ns_per_s); 
        std.debug.print("Main thread working...\n", .{});
    }
    
    std.debug.print("All work done! Zig is cool!\n", .{});
}

fn thread_function(num: usize) !void {
    std.debug.print("thread {d}: started.\n", .{num});
    
    // 模拟耗时工作
    // 注意:std.time.sleep 接收纳秒
    std.time.sleep(1 * std.time.ns_per_s);

    std.debug.print("thread {d}: finished.\n", .{num});
}

核心知识点总结

1. spawnjoin

  • spawn: 向操作系统申请创建一个新线程。这涉及到系统调用,有一定的开销。
  • join: 阻塞当前线程,直到目标线程退出。它同时负责清理线程栈等资源。如果你不关心线程何时结束,可以使用 detach(),但那样你就失去了对它的控制。

2. 这里的 defer 陷阱

在这个简单的例子中,我们对每个线程立即调用了 defer join。 由于 defer 是后进先出(LIFO)执行的,且是在作用域结束时执行。 这意味着主线程会在 } 处依次等待 handle3, handle2, handle1。虽然等待是串行的,但线程本身的执行是并行的。

3. 数据竞争 (Data Race)

虽然本例没有涉及共享数据,但在多线程编程中,多个线程同时修改同一个变量是非常危险的。Zig 提供了 std.Thread.Mutex(互斥锁)等工具来保护临界区。在后续的进阶学习中,这是必须掌握的内容。


后续预告:我们已经学会了如何创建线程。但是,如果线程之间需要说话怎么办?比如线程 A 生产数据,线程 B 处理数据?下一篇,我们将可能会接触 Atomic (原子操作) 或者 Channel (通道) 的概念(如果 Ziglings 包含的话),或者回归标准库的其他实用功能。