# std.Thread and std.Io Synchronization (Zig 0.16.0) Primary release-note source: https://ziglang.org/download/0.16.0/release-notes.html Zig 0.16 keeps `std.Thread` for OS thread spawning and thread utilities, but blocking synchronization and task concurrency should move to `std.Io` when it may block inside code that participates in the I/O runtime. ## What Remains in std.Thread Use `std.Thread` for explicit OS threads: ```zig const std = @import("std"); fn worker(id: usize) void { std.debug.print("worker {d}\n", .{id}); } pub fn main() !void { const thread = try std.Thread.spawn(.{}, worker, .{42}); thread.join(); } ``` Useful thread utilities: ```zig const id = std.Thread.getCurrentId(); const cpu_count = std.Thread.getCpuCount() catch 1; std.Thread.sleep(10 * std.time.ns_per_ms); std.Thread.yield() catch {}; ``` ## Removed or Avoided std.Thread APIs - `std.Thread.Pool` was removed. - `std.Thread.Mutex.Recursive` was removed. - `std.once` was removed. - Blocking synchronization in I/O-aware code should use `std.Io` primitives. ## std.Io Sync Migration Release-note migration map: | Old | New | |-----|-----| | `std.Thread.Mutex` | `std.Io.Mutex` | | `std.Thread.Condition` | `std.Io.Condition` | | `std.Thread.Semaphore` | `std.Io.Semaphore` | | `std.Thread.RwLock` | `std.Io.RwLock` | | `std.Thread.ResetEvent` | `std.Io.Event` | | `std.Thread.WaitGroup` | `std.Io.Group` | | `std.Thread.Futex` | `std.Io.Futex` | Lock-free atomics do not need `std.Io`. ## Mutex Use cancelable locking when cancelation should be honored: ```zig var mutex: std.Io.Mutex = .init; var value: u64 = 0; fn increment(io: std.Io) !void { try mutex.lock(io); defer mutex.unlock(io); value += 1; } ``` Use uncancelable locking for short critical sections where interruption would corrupt state or skip required cleanup: ```zig mutex.lockUncancelable(io); defer mutex.unlock(io); ``` Do not replace mutexes with spin loops unless there is a documented, measured special case. ## Condition `std.Io.Condition` waits with an `io` and a `std.Io.Mutex`. ```zig var mutex: std.Io.Mutex = .init; var condition: std.Io.Condition = .init; var ready = false; fn waitUntilReady(io: std.Io) !void { try mutex.lock(io); defer mutex.unlock(io); while (!ready) { try condition.wait(io, &mutex); } } fn setReady(io: std.Io) void { mutex.lockUncancelable(io); defer mutex.unlock(io); ready = true; condition.broadcast(io); } ``` ## Semaphore ```zig var semaphore: std.Io.Semaphore = .{ .permits = 3 }; fn usePermit(io: std.Io) !void { try semaphore.wait(io); defer semaphore.post(io); // protected limited-concurrency work } ``` Use `waitUncancelable(io)` only when a cancellation point would be invalid. ## RwLock ```zig var rw: std.Io.RwLock = .init; fn read(io: std.Io) !void { try rw.lockShared(io); defer rw.unlockShared(io); } fn write(io: std.Io) !void { try rw.lock(io); defer rw.unlock(io); } ``` ## Event `std.Thread.ResetEvent` maps to `std.Io.Event`. ```zig var event: std.Io.Event = .unset; fn waiter(io: std.Io) !void { try event.wait(io); } fn signal(io: std.Io) void { event.set(io); } ``` ## Group Instead of WaitGroup / Thread.Pool Use `std.Io.Group` to spawn and await related tasks. ```zig fn doWork(io: std.Io) !void { var group: std.Io.Group = .init; errdefer group.cancel(io); group.async(io, worker, .{ io, 0 }); group.async(io, worker, .{ io, 1 }); try group.await(io); } fn worker(io: std.Io, id: usize) void { _ = .{ io, id }; } ``` Do not mechanically replace `std.Thread.Pool` with `std.Io.Group` if tasks synchronously depend on the caller or mutate shared state without I/O-aware synchronization. Re-evaluate ownership and synchronization first. ## Cancelation Cancelable waits/locks may return `error.Canceled`. Rules: - Propagate `error.Canceled` by default. - Only ignore it in code that requested cancelation. - If you catch it and continue, use `io.recancel()` when the request should remain active. - Use uncancelable waits/locks sparingly and only where cancellation would violate invariants. ## Application Guidance - Queues, allocators, registries, timers, and worker systems that use blocking sync should accept/store `std.Io`. - Use `lockUncancelable(io)` for queue integrity and allocator metadata updates when cancellation cannot safely interrupt the critical section. - Keep OS threads for code that is explicitly thread-owned, such as dedicated worker threads or external API callbacks. - Prefer an application's existing scheduler for application work unless the standard `std.Io` task API is explicitly the better fit. ## Review Checklist - Is this code in an I/O-aware path? If yes, sync primitives should be `std.Io.*`. - Does every blocking lock/wait receive an `io`? - Is cancelation either propagated or deliberately protected? - Was `std.Thread.Pool` replaced with a design that preserves ordering and synchronization semantics? - Are atomics used only for lock-free state where blocking is not required?