# Threads

> Real OS threads, and why this page cannot run in your browser.

```zig
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();
}
```

*Built and run natively by CI. wasm32-wasi is single-threaded, so std.Thread.spawn does not compile for it at all. (`03-standard-library.threads`)*

## Spawn and join

```zig
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:

```zig
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. See
[Cancellation](https://www.ziglang.in/learn/standard-library/cancellation/) for what that means.

## Atomics for the simple cases

A counter does not need a mutex:

```zig
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`](https://www.ziglang.in/learn/standard-library/concurrency/) 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.
