Zig 的语法设计简洁直观,旨在提供高效、易读和安全的编程体验。以下是对 Zig 语法的详细介绍:
注释
单行注释
单行注释以 //
开头,从 //
到行末的所有内容都会被视为注释。
多行注释
多行注释以 /*
开头,以 */
结束,注释内容可以跨越多行。
嵌套注释
Zig 允许嵌套多行注释,这在临时禁用代码块或调试时非常有用。
在上面的代码中,嵌套的多行注释使得整个注释块内的代码都不会被执行。
注释的最佳实践
- 解释代码意图:注释应解释代码的意图,而不仅仅是重复代码的字面意思。
- 保持简洁:注释应简洁明了,避免冗长。
- 更新注释:在修改代码时,确保相应的注释也被更新。
- 使用 TODO 注释:使用
TODO
注释标记需要进一步改进或实现的地方。
变量声明
Zig 的变量声明语法简洁且灵活,支持显式类型声明和类型推断。以下是对 Zig 变量声明的详细介绍:
基本变量声明
常量声明
使用 const
关键字声明常量。常量一旦声明,其值不可更改。
变量声明
使用 var
关键字声明变量。变量的值可以在程序运行过程中改变。
变量的类型推断
Zig 编译器可以根据初始值自动推断变量的类型。以下是一些例子:
指针变量
Zig 支持指针变量,用于引用内存地址。使用 *
符号表示指针类型。
数组变量
Zig 支持定长数组和切片(slice)变量。
结构体变量
Zig 允许声明和使用结构体类型的变量,用于组织相关的数据。
可选变量
Zig 支持可选类型(optional),表示变量可能有值,也可能没有值。使用 ?
表示可选类型。
示例代码
下面是一个包含各种变量声明的完整示例:
数据类型
以下是 Zig 数据类型的表格,并与 C 语言的对应类型进行对比。此表格涵盖了常见的基础类型、浮点类型、布尔类型和指针类型。
Zig 类型 | 描述 | C 类型 | 示例 |
---|
整数类型 | | | |
iN | N 位有符号整数 | 无直接对应 | const a: i12 = -128; |
uN | N 位无符号整数 | 无直接对应 | const a: u12 = 128; |
i8 | 8 位有符号整数 | int8_t / signed char | const a: i8 = -128; |
i8 | 8 位有符号整数 | int8_t / signed char | const a: i8 = -128; |
u8 | 8 位无符号整数 | uint8_t / unsigned char | const b: u8 = 255; |
i16 | 16 位有符号整数 | int16_t / short | const c: i16 = 32767; |
u16 | 16 位无符号整数 | uint16_t / unsigned short | const d: u16 = 65535; |
i32 | 32 位有符号整数 | int32_t / int | const e: i32 = -2147483648; |
u32 | 32 位无符号整数 | uint32_t / unsigned int | const f: u32 = 4294967295; |
i64 | 64 位有符号整数 | int64_t / long long | const g: i64 = 9223372036854775807; |
u64 | 64 位无符号整数 | uint64_t / unsigned long long | const h: u64 = 18446744073709551615; |
i128 | 128 位有符号整数 | __int128 | const h: i128 = 18446744073709551615; |
u128 | 128 位无符号整数 | unsigned __int128 | const h: u128 = 18446744073709551615; |
isize | 有符号指针大小的整数 | intptr_t | const h: isize = 18446744073709551615; |
usize | 无符号指针大小的整数 | uintptr_t / size_t | const h: usize = 18446744073709551615; |
c_char | 与c语言的char兼容 | char | const h: char = 1; |
c_short | 与c语言的short兼容 | short | const h: c_short = 1; |
c_ushort | 与c语言的unsigned short兼容 | unsigned short | const h: c_ushort = 1; |
c_int | 与c语言的int兼容 | int | const h: c_int = 1; |
c_uint | 与c语言的unsigned int兼容 | unsigned int | const h: c_uint = 1; |
c_long | 与c语言的long兼容 | long | const h: c_long = 1; |
c_ulong | 与c语言的unsigned long兼容 | unsigned long | const h: c_ulong = 1; |
c_longlong | 与c语言的long long兼容 | long long | const h: c_longlong = 1; |
c_ulonglong | 与c语言的unsigned long long兼容 | unsigned long long | const h: c_ulonglong = 1; |
浮点类型 | | | |
f16 | 16 位浮点数 | 无直接对应(半精度) | const i: f16 = 1.5; |
f32 | 32 位浮点数 | float | const j: f32 = 3.14; |
f64 | 64 位浮点数 | double | const k: f64 = 2.71828; |
f80 | 80 位浮点数 | 无直接对应 | const k: f80 = 2.71828; |
f128 | 128 位浮点数 | 无直接对应 | const k: f128 = 2.71828; |
c_longdouble | 与c语言的long double兼容 | long double | const k: c_longdouble = 2.71828; |
布尔类型 | | | |
bool | 布尔类型,值为 true 或 false | _Bool / bool | const l: bool = true; |
指针类型 | | | |
*T | 指向类型 T 的指针 | T* | var m: *i32 = &n; |
?*T | 可选指针,指向类型 T 或者 null | T* 或 NULL | var o: ?*i32 = null; |
[*]T | 指向类型 T 的数组的指针 | T* | const p: [*]const u8 = "hello"; |
数组和切片 | | | |
[N]T | 定长数组,包含 N 个类型 T 的元素 | T[N] | const q: [3]i32 = [3]i32{1, 2, 3}; |
[]T | 切片,动态长度数组,包含类型 T 的元素 | 无直接对应 | var r: []i32 = q[0..]; |
结构体类型 | | | |
struct | 结构体类型,包含多个字段 | struct | const Point = struct { x: i32, y: i32 }; |
联合类型 | | | |
union | 联合类型,共享同一内存位置的多个字段 | union | const MyUnion = union { a: i32, b: f32 }; |
可选类型 | | | |
?T | 可选类型,包含类型 T 或者 null | T 或 NULL | var s: ?i32 = null; |
错误类型 | | | |
!T | 错误类型,返回类型 T 或者错误 | int / errno | fn foo() !void { return error.Failed; } |
其他类型 | | | |
void | 无返回值类型 | void | fn bar() void {} |
noreturn | 不返回类型,表示函数不会返回 | void (用于 exit 等函数) | fn terminate() noreturn {} |
anyopaque | 用于类型擦除指针 | void | 与c语言void互操作 |
type | 类型的类型 | 无直接对应 | |
anyerror | 类型的类型 | 无直接对应 | |
comptime_int | 仅允许使用comptime已知值。整数字面量的类型 | 无直接对应 | |
comptime_float | 仅允许使用comptime已知值。浮点文字的类型 | 无直接对应 | |
示例代码
以下是一个包含多种数据类型声明的完整示例,并展示了如何与 C 语言类型对应:
类型转换
在 Zig 中,类型转换是将一个值从一种数据类型转换为另一种数据类型的过程。Zig 提供了多种类型转换的方法,包括显式类型转换、隐式类型转换、类型推断和使用内置函数进行类型转换。以下是对 Zig 中类型转换的详细介绍。
显式类型转换
显式类型转换需要使用类型转换函数进行明确的转换。Zig 提供了一些内置函数用于类型转换:
@intCast
: 将整数类型转换为另一种整数类型。
@floatCast
: 将浮点类型转换为另一种浮点类型。
@intToFloat
: 将整数类型转换为浮点类型。
@floatToInt
: 将浮点类型转换为整数类型。
整数类型转换
浮点类型转换
整数到浮点类型转换
浮点到整数类型转换
指针类型转换
Zig 允许不同指针类型之间的转换,可以使用 @ptrCast
进行指针类型转换。
枚举类型转换
枚举类型可以使用 @enumToInt
和 @intToEnum
进行转换。
类型推断
Zig 支持类型推断,编译器可以根据上下文自动推断出变量的类型。尽管这不是严格意义上的类型转换,但在许多情况下可以减少显式类型转换的需要。
处理错误类型的转换
Zig 的错误处理机制可以通过 catch
关键字进行转换处理。
字符串格式化
在Zig中,字符串格式化可以通过内置的 std.fmt
模块实现。以下是几种常见的字符串格式化方法:
1. 使用 std.fmt
模块的 fmt
函数
这是Zig中用于字符串格式化的主要方式。std.fmt
提供了类似于C语言 printf
风格的格式化方法。
2. 使用 std.fmt
模块的 bufPrint
函数
这种方法适用于将格式化字符串写入到缓冲区中。
3. 使用 std.fmt
模块的 print
函数
如果只是简单地将格式化字符串输出到标准输出,可以直接使用 std.fmt.print
函数。
4. 使用 std.mem
模块的字符串操作函数
对于简单的字符串拼接,可以使用 std.mem
模块中的一些基本字符串操作函数。
运算符
在 Zig 中,运算符是用于执行各种操作的符号或关键词。以下是 Zig 语言中的主要运算符及其用途的表格:
算术运算符
运算符 | 描述 | 示例 | 结果 |
---|
+ | 加法 | 5 + 3 | 8 |
- | 减法 | 5 - 3 | 2 |
* | 乘法 | 5 * 3 | 15 |
/ | 除法 | 6 / 3 | 2 |
% | 取模(余数) | 5 % 3 | 2 |
** | 幂运算 | 2 ** 3 | 8 |
- | 取负 | -5 | -5 |
关系运算符
运算符 | 描述 | 示例 | 结果 |
---|
== | 等于 | 5 == 3 | false |
!= | 不等于 | 5 != 3 | true |
> | 大于 | 5 > 3 | true |
< | 小于 | 5 < 3 | false |
>= | 大于等于 | 5 >= 3 | true |
<= | 小于等于 | 5 <= 3 | false |
逻辑运算符
运算符 | 描述 | 示例 | 结果 |
---|
&& | 逻辑与 | true && false | false |
` | | ` | 逻辑或 |
! | 逻辑非 | !true | false |
位运算符
运算符 | 描述 | 示例 | 结果 |
---|
& | 位与 | 5 & 3 | 1 |
` | ` | 位或 | 5 | 3 |
^ | 位异或 | 5 ^ 3 | 6 |
~ | 位非 | ~5 | -6 |
<< | 左移 | 5 << 1 | 10 |
>> | 右移 | 5 >> 1 | 2 |
赋值运算符
运算符 | 描述 | 示例 | 结果 |
---|
= | 赋值 | x = 5 | x 是 5 |
+= | 加并赋值 | x += 3 | x 增加 3 |
-= | 减并赋值 | x -= 3 | x 减少 3 |
*= | 乘并赋值 | x *= 3 | x 乘以 3 |
/= | 除并赋值 | x /= 3 | x 除以 3 |
%= | 取模并赋值 | x %= 3 | x 取模 3 |
条件运算符
Zig 采用类似于其他语言的条件运算符和语句,但没有传统的三元运算符。使用 if-else
语句实现条件逻辑:
特殊运算符
运算符 | 描述 | 示例 | 结果 |
---|
@ | 内置函数调用前缀 | @sizeOf(i32) | 4 |
? | 可选类型处理 | value ? 0 | value 不是 null 时返回 value,否则返回 0 |
try | 错误处理 | try foo() | 调用 foo() ,如果返回错误,则传播错误 |
catch | 捕获错误 | foo() catch 0 | 调用 foo() ,如果返回错误,则返回 0 |
示例代码
函数
在 Zig 中,函数是代码的基本构建块,用于执行特定的任务。函数根据其特性和使用方式可以分为多种类型。以下是对 Zig 中函数分类的详细介绍:
基本函数
普通函数
普通函数是最常见的函数类型,用于执行一般任务,可以有参数和返回值。
void 函数
void 函数没有返回值,仅执行一些操作。
类型分类
泛型函数
泛型函数可以处理多种类型,通过在函数签名中使用 comptime
关键字定义类型参数。
内联函数
内联函数使用 inline
关键字提示编译器将函数体直接嵌入调用点,以减少函数调用的开销。
特殊功能分类
编译时函数
编译时函数使用 comptime
关键字,可以在编译时执行。这在元编程中非常有用。
错误处理函数
错误处理函数返回一个可能包含错误的值,使用 !
表示。
递归函数
递归函数是调用自身的函数,通常用于解决递归定义的问题。
上下文相关分类
成员函数
成员函数是定义在结构体或联合体内的函数,通常操作该结构体或联合体的实例。
示例代码
下面是一个包含各种类型函数的完整示例:
控制流
在 Zig 中,控制流语句用于控制程序的执行顺序,包括条件判断、循环和错误处理等。以下是 Zig 语言中常见的控制流语句及其使用方法的详细介绍:
条件判断
if-else 语句
if-else
语句根据条件表达式的真假执行不同的代码块。
嵌套 if-else 语句
可以嵌套多个 if-else
语句以处理多个条件。
switch 语句
switch
语句用于基于不同的值执行不同的代码块。
循环
while 循环
while
循环在条件为真时重复执行代码块。
for 循环
for
循环用于遍历数组、切片或其他可迭代对象。
提前返回
return
return
语句用于提前终止函数执行并返回结果。
断言
assert
assert
用于在调试过程中检查条件是否为真,条件为假时会引发运行时错误。
示例代码
下面是一个综合示例,展示了条件判断、循环和错误处理的使用:
内存管理
在 Zig 中,内存管理是一个关键方面,允许开发者在需要时精确控制内存的分配和释放。Zig 提供了一些内置的内存管理功能,通过显式的内存分配和释放,确保代码的安全性和高效性。以下是关于 Zig 内存管理的详细介绍:
内存分配和释放
使用标准库分配器
Zig 的标准库提供了各种内存分配器,最常用的是 std.heap.page_allocator
。可以使用这些分配器来动态分配和释放内存。
自定义分配器
可以自定义分配器以满足特定的内存管理需求。例如,可以实现一个简单的分配器来管理固定大小的内存块。
内存对齐
Zig 支持显式内存对齐,可以使用 @alignCast
将内存块对齐到特定边界。
结构体中的内存管理
在结构体中,可以使用内存分配器来管理动态数据,并在结构体销毁时释放内存。
结构体
在 Zig 中,结构体(struct)是一种用户定义的数据类型,用于将多个相关的数据组合在一起。结构体可以包含字段、方法和嵌套的结构体。结构体在组织和管理数据时非常有用,特别是在处理复杂的数据结构时。以下是对 Zig 结构体的详细介绍。
定义结构体
结构体使用 struct
关键字定义,字段可以是任何类型,包括基本类型、数组、切片、指针、其他结构体等。
结构体方法
可以在结构体内定义方法,方法可以访问结构体的字段。方法的第一个参数通常是 self
,指向结构体的实例。
结构体初始化
结构体可以使用显式初始化列表进行初始化。也可以定义初始化函数进行更复杂的初始化操作。
嵌套结构体
结构体可以嵌套其他结构体,从而构建更复杂的数据结构。
使用 self
和 *self
在结构体方法中,使用 self
表示结构体实例。可以使用 self
和 *self
来访问结构体的字段和方法。
self
用于不可变方法,表示当前实例的一个不可变引用。
*self
用于可变方法,表示当前实例的一个可变引用。
使用默认值初始化结构体字段
在初始化结构体时,可以为字段提供默认值。
错误处理
在 Zig 中,错误处理是语言设计的一部分,通过显式的错误处理机制提高代码的可读性和可靠性。以下是对 Zig 中错误处理的详细介绍,包括错误类型、错误传播、错误捕获、以及错误处理的最佳实践。
错误类型
Zig 使用 error
关键字定义错误类型。错误类型通常用于函数的返回类型,表示函数可能返回一个错误。
错误传播
在 Zig 中,函数可以返回错误,使用 !
表示返回值可能是一个错误。可以使用 try
关键字来调用可能返回错误的函数,自动传播错误。
错误捕获
可以使用 catch
关键字捕获并处理错误。如果捕获到错误,可以执行特定的代码块来处理错误。
错误处理示例
下面是一个综合示例,展示了如何在函数中使用错误处理机制,以及如何在主函数中捕获和处理这些错误。
使用 defer
和 errdefer
defer
和 errdefer
关键字可以用来确保在函数返回前执行某些操作,常用于资源释放和清理工作。
defer
总是在函数返回前执行。
errdefer
仅在函数因错误返回时执行。
使用自定义错误处理函数
可以定义一个函数来处理特定类型的错误,以便在不同的地方重复使用。
编译时执行
在 Zig 中,编译时执行(comptime execution)是一个强大的功能,允许在编译期间执行代码。这在元编程、优化和代码生成方面非常有用。以下是对 Zig 编译时执行的详细介绍。
编译时执行概述
编译时执行可以用于:
使用 comptime
关键字
可以使用 comptime
关键字在编译时执行代码。任何在 comptime
块中的代码都会在编译时执行。
编译时常量计算
编译时类型检查
编译时函数
可以在编译时执行函数,并且可以在函数签名中使用 comptime
关键字指定编译时参数。
@compileTime
内置函数
@compileTime
可以用于检查代码是否在编译时执行。
编译时循环和条件
编译时执行还支持循环和条件语句,用于生成代码。
使用编译时执行进行元编程
编译时执行特别适合用于元编程,可以生成复杂的数据结构或函数代码。
编译时执行和泛型
编译时执行可以与泛型结合使用,根据编译时参数生成不同的代码。
泛型和类型推断
在 Zig 中,泛型和类型推断提供了强大的工具来编写灵活且可重用的代码。以下是对 Zig 中泛型和类型推断的详细介绍。
泛型函数
泛型函数允许你编写对多种类型都适用的函数。通过在函数签名中使用 comptime
关键字,可以定义泛型参数。
基本泛型函数
泛型数据结构
可以使用泛型定义数据结构,使其适用于不同类型的数据。
类型推断
Zig 支持类型推断,可以在变量声明时根据初始值自动推断其类型。使用类型推断可以减少代码冗余,提升代码可读性。
基本类型推断
函数返回值类型推断
Zig 也支持在函数定义中推断返回值类型。如果返回值类型可以从函数体中推断出来,则可以省略返回值类型。
泛型与类型推断结合
泛型和类型推断可以结合使用,使函数和数据结构更加灵活和通用。
泛型结构体和类型推断
结合泛型结构体和类型推断,可以创建高度灵活的数据结构。
模块和导入
在 Zig 中,模块和导入机制提供了组织和重用代码的有效方法。模块化设计允许开发者将代码分割成独立的部分,每个部分都有自己的作用域和依赖关系。以下是关于 Zig 中模块和导入机制的详细介绍。
模块
在 Zig 中,模块是代码的一个独立单元,通常对应于一个文件。模块可以包含常量、变量、函数、结构体和其他类型的定义。
创建模块
假设我们有一个名为 math.zig
的模块,包含一些数学函数:
在这个例子中,math.zig
定义了两个公共函数 add
和 subtract
。
导入模块
使用 @import
函数导入模块,并在代码中使用模块中的定义。
导入自定义模块
在这个例子中,我们导入了 math.zig
模块,并调用了其中的 add
和 subtract
函数。
导入标准库
Zig 提供了一个强大的标准库,可以通过 @import("std")
导入。
在这个例子中,我们使用标准库中的 heap.page_allocator
和 mem.len
函数来分配内存和计算字符串长度。
公共和私有符号
在 Zig 中,可以使用 pub
关键字将模块中的符号声明为公共符号,使其可以被其他模块访问。如果不使用 pub
,符号将是私有的,只有在模块内部可以访问。
公共符号
私有符号
在这个例子中,PI
和 square
是私有的,只有 math.zig
模块内部可以访问。
嵌套模块
可以在模块内部定义子模块,以进一步组织代码。
使用嵌套模块的例子:
示例项目结构
以下是一个示例项目结构,展示如何组织模块和导入它们:
main.zig
math.zig
utils.zig
标准库
Zig 的标准库(Standard Library)提供了一套丰富的功能,涵盖了从基本数据类型和集合到内存管理和 I/O 操作等多个方面。以下是对 Zig 标准库主要模块和功能的详细介绍。
导入标准库
在 Zig 中,标准库可以通过 @import("std")
进行导入:
常见模块
基本模块
std.mem
: 提供了内存操作函数。
std.math
: 提供了常见的数学函数和常量。
std.c
: 提供了 C 标准库的绑定。
std.fmt
: 提供了字符串格式化函数。
数据结构
std.ArrayList
: 动态数组,实现了可变长度的数组。
std.HashMap
: 哈希表,实现了键值对的存储和检索。
std.LinkedList
: 链表,实现了元素的有序存储。
I/O 操作
std.io
: 提供了输入输出操作的支持,包括文件读写和标准输入输出。
std.fs
: 提供了文件系统操作的支持,包括文件和目录的创建、删除等。
并发和同步
std.Thread
: 提供了线程操作的支持。
std.sync
: 提供了同步原语,包括互斥锁和条件变量。
标准库使用示例
内存操作
std.mem
提供了一些常用的内存操作函数。
数学函数
std.math
提供了常见的数学函数。
动态数组
std.ArrayList
提供了动态数组的实现。
文件操作
std.fs
提供了文件系统操作的支持。
线程操作
std.Thread
提供了线程操作的支持。
同步原语
std.sync
提供了同步原语,包括互斥锁和条件变量。
内置函数
在 Zig 中,内置函数(builtin functions)是由语言本身提供的一组函数,通常用于低级别的操作、类型检查和转换、编译时操作等。内置函数的名称以 @
开头。以下是对 Zig 中一些常见内置函数的详细介绍及示例。
内置函数分类
类型转换和检查
@intCast
: 将一个整数类型转换为另一种整数类型。
@floatCast
: 将一个浮点类型转换为另一种浮点类型。
@intToFloat
: 将一个整数类型转换为浮点类型。
@floatToInt
: 将一个浮点类型转换为整数类型。
@bitCast
: 在不同类型之间进行位级别的转换。
@typeOf
: 获取值的类型。
@sizeOf
: 获取类型或值的字节大小。
@alignOf
: 获取类型或值的对齐要求。
@field
: 访问结构体的字段。
数学和位操作
@minValue
: 获取类型的最小值。
@maxValue
: 获取类型的最大值。
@divExact
: 整数除法,如果不能整除则引发错误。
@divFloor
: 向下取整的整数除法。
@mod
: 获取整数除法的余数。
@shlExact
: 左移操作,如果移位超出范围则引发错误。
@shrExact
: 右移操作,如果移位超出范围则引发错误。
@bitReverse
: 反转位顺序。
编译时操作
@compileTime
: 判断代码是否在编译时执行。
@typeName
: 获取类型的名称。
@hasField
: 判断结构体是否具有某个字段。
@fieldParentPtr
: 获取包含字段的结构体指针。
@inlineCall
: 内联调用一个函数。
内存和指针操作
@ptrCast
: 将指针转换为另一种指针类型。
@alignCast
: 将指针对齐到指定的边界。
@byteOffsetOf
: 获取字段在结构体中的字节偏移量。
@ptrToInt
: 将指针转换为整数。
内置函数示例
类型转换
获取类型信息
数学和位操作
编译时操作
内存和指针操作
并发编程
多进程
Zig 编程语言支持多进程编程,可以通过 Zig 提供的标准库和系统调用实现多进程。以下是一个简单的例子,展示如何使用 Zig 创建和管理多个进程。
这个示例展示了如何使用 Zig 创建一个子进程并等待它完成。这个简单的程序会启动一个 echo
命令,然后等待它完成并打印结果。
解释
-
初始化和清理:
- 使用
std.ChildProcess.init
初始化一个子进程对象。
- 使用
defer child_process.deinit()
确保在程序结束时清理资源。
-
设置命令:
child_process.argv
设置要执行的命令及其参数,这里是 echo "Hello, World!"
。
-
启动进程:
child_process.spawn()
启动子进程。
-
等待进程结束:
child_process.wait()
等待子进程结束并获取其退出状态。
多线程
在 Zig 编程语言中,多线程编程可以通过标准库中的 std.Thread
模块来实现。以下是一个简单的例子,展示如何使用 Zig 创建和管理多个线程。
解释
-
导入标准库:
const std = @import("std");
导入 Zig 的标准库。
-
线程函数:
fn threadFunc(arg: usize) void
定义一个线程函数,它接收一个参数 arg
,并打印线程的编号。
-
主函数:
pub fn main() void
定义主函数。
-
创建线程数组:
var threads: [num_threads]std.Thread = undefined;
定义一个 std.Thread
类型的数组,用于存储线程对象。
-
创建多个线程:
- 使用
for
循环创建多个线程,每个线程运行 threadFunc
函数,并传递线程编号作为参数。
-
等待所有线程结束:
- 使用另一个
for
循环等待所有线程结束,确保主程序在所有线程结束后才退出。
这个例子展示了如何在 Zig 中创建和管理多个线程。你可以根据需要修改线程函数 threadFunc
,实现更复杂的并行计算或任务处理。
线程安全和同步
在多线程编程中,线程安全和数据同步是关键问题。Zig 提供了一些工具来帮助实现线程同步,例如 std.Mutex
和 std.Cond
.
以下是一个使用 std.Mutex
的例子,展示如何实现线程同步:
异步编程(zig开发中..)