Threads
const std = @import("std");
fn addTo(total: *u32, amount: u32) void {
total.* += amount;
}
fn addLocked(total: *u32, mutex: *std.Io.Mutex, io: std.Io, amount: u32) void {
mutex.lock(io) catch return;
defer mutex.unlock(io);
total.* += amount;
}
pub fn main(init: std.process.Init) !void {
const io = init.io;
var buf: [256]u8 = undefined;
var file_writer = std.Io.File.stdout().writerStreaming(io, &buf);
const out = &file_writer.interface;
// One thread, joined explicitly.
var value: u32 = 0;
const thread = try std.Thread.spawn(.{}, addTo, .{ &value, 42 });
thread.join();
try out.print("single: {d}\n", .{value});
// Many threads touching one variable need synchronisation; without the
// mutex this is a data race and the total is unreliable.
var total: u32 = 0;
var mutex: std.Io.Mutex = .init;
var threads: [8]std.Thread = undefined;
for (&threads) |*t| {
t.* = try std.Thread.spawn(.{}, addLocked, .{ &total, &mutex, io, 1 });
}
for (threads) |t| t.join();
try out.print("locked total: {d}\n", .{total});
// Atomics cover the simple counter case without a lock.
var counter = std.atomic.Value(u32).init(0);
_ = counter.fetchAdd(1, .monotonic);
_ = counter.fetchAdd(1, .monotonic);
try out.print("atomic: {d}\n", .{counter.load(.monotonic)});
try out.flush();
}Spawn and join
const thread = try std.Thread.spawn(.{}, function, .{ args... });
thread.join();
The arguments are a tuple, matching the function’s parameters. join blocks
until the thread finishes; detach gives up the handle instead.
Synchronisation moved to std.Io
This is the part that breaks older code. std.Thread.Mutex no longer exists.
It is std.Io.Mutex now, and lock takes the io instance:
var mutex: std.Io.Mutex = .init;
try mutex.lock(io);
defer mutex.unlock(io);
lock can fail only because it can be cancelled, which is what makes it
composable with the rest of the Io machinery.
Atomics for the simple cases
A counter does not need a mutex:
var counter = std.atomic.Value(u32).init(0);
_ = counter.fetchAdd(1, .monotonic);
Note the _ =: fetchAdd returns the previous value, and Zig will not let
you drop it silently.
Prefer structured concurrency
For most work, Io.async and Io.Group are
a better fit than spawning raw threads: they are cancellable, they cannot leak
a running task past their scope, and they work on targets that have no threads
at all, including the one this site runs on.