Io Implementations
The starting point for async code is the std.Io
interface. Init.io provides a default implementation, but
we can also choose one explicitly:
std.Io.Threaded uses a thread poool that adds more threads as needed, up to a configurable maximum.
std.Io.Threaded.init_single_threaded only uses the current thread, so functions started with io.async don’t actually run asynchronously.
std.Io.Evented uses an event-based interface provided by the operating system. In Zig 0.16, this is considered “work-in-progress”.
const std = @import("std");
const print = std.debug.print;
const builtin = @import("builtin");
// As before, 'sleep' simulates a slow operation.
fn sleep(ms: isize) void {
var ts: std.posix.timespec =
.{ .sec = 0, .nsec = ms * 1000000 };
_ = std.posix.system.nanosleep(&ts, &ts);
}
fn task(count: u64, label: []const u8) void {
for (0..count) |_| {
print("{s}\n", .{label});
sleep(100);
}
}
// 'run' starts two tasks and waits for them to finish.
fn run(io: std.Io) void {
var a = io.async(task, .{ 4, "aaa" });
var b = io.async(task, .{ 4, " bbb" });
a.await(io);
b.await(io);
}
fn runThreaded(allocator: std.mem.Allocator) !void {
var th = std.Io.Threaded.init(allocator, .{});
defer th.deinit();
run(th.io());
}
fn runSingleThreaded() !void {
var th = std.Io.Threaded.init_single_threaded;
defer th.deinit();
run(th.io());
}
fn runEvented(allocator: std.mem.Allocator) !void {
var ev: std.Io.Evented = undefined;
try ev.init(allocator, .{});
defer ev.deinit();
run(ev.io());
}
pub fn main(init: std.process.Init) !void {
print("Threaded:\n", .{});
try runThreaded(init.gpa);
print("\nSingle-threaded:\n", .{});
try runSingleThreaded();
// std.Io.Evented is not (yet) working on Linux and
// Windows, so we’ll skip it there.
switch (comptime builtin.os.tag) {
.linux, .windows => {},
else => {
print("\nEvented:\n", .{});
try runEvented(init.gpa);
},
}
}Running on Linux:
$ zig run io.zig
Threaded:
bbb
aaa
aaa
bbb
aaa
bbb
aaa
bbb
Single-threaded:
aaa
aaa
aaa
aaa
bbb
bbb
bbb
bbb
Next example: Queue.