# Threads

### Threads

A thread is an independent flow of execution.

A Zig program begins with one thread: the main thread. Additional threads may be created to perform work in parallel.

A thread runs a function.

Here is the smallest useful example:

```zig
const std = @import("std");

fn worker() void {
    std.debug.print("worker thread\n", .{});
}

pub fn main() !void {
    const thread = try std.Thread.spawn(.{}, worker, .{});

    thread.join();
}
```

Run it:

```sh
zig run main.zig
```

The output is:

```text
worker thread
```

`std.Thread.spawn` creates a new operating system thread.

The first argument contains thread options:

```zig
.{}
```

This example uses the default options.

The second argument is the function to run:

```zig
worker
```

The third argument is a tuple containing the function arguments:

```zig
.{}
```

`worker` takes no parameters, so the tuple is empty.

The return value of `spawn` is a `Thread` object:

```zig
const thread = try std.Thread.spawn(...);
```

The thread begins executing immediately.

The call:

```zig
thread.join();
```

waits for the thread to finish.

If `join` is removed, the program may exit before the worker thread completes.

A thread function may take parameters:

```zig
const std = @import("std");

fn printNumber(n: u32) void {
    std.debug.print("number = {d}\n", .{n});
}

pub fn main() !void {
    const thread = try std.Thread.spawn(
        .{},
        printNumber,
        .{42},
    );

    thread.join();
}
```

The tuple:

```zig
.{42}
```

provides the arguments passed to the thread function.

Multiple threads may run at the same time:

```zig
const std = @import("std");

fn worker(id: u32) void {
    std.debug.print("worker {d} starting\n", .{id});
}

pub fn main() !void {
    const t1 = try std.Thread.spawn(.{}, worker, .{1});
    const t2 = try std.Thread.spawn(.{}, worker, .{2});
    const t3 = try std.Thread.spawn(.{}, worker, .{3});

    t1.join();
    t2.join();
    t3.join();
}
```

The output order is not guaranteed.

One run may produce:

```text
worker 1 starting
worker 2 starting
worker 3 starting
```

Another may produce:

```text
worker 3 starting
worker 1 starting
worker 2 starting
```

Threads execute concurrently. The operating system scheduler decides when each thread runs.

A thread shares memory with other threads in the same process.

This makes communication easy, but it also introduces problems.

Consider this program:

```zig
const std = @import("std");

var counter: u32 = 0;

fn increment() void {
    var i: u32 = 0;

    while (i < 100000) : (i += 1) {
        counter += 1;
    }
}

pub fn main() !void {
    const t1 = try std.Thread.spawn(.{}, increment, .{});
    const t2 = try std.Thread.spawn(.{}, increment, .{});

    t1.join();
    t2.join();

    std.debug.print("{d}\n", .{counter});
}
```

You might expect:

```text
200000
```

But the result is undefined.

Two threads modify `counter` at the same time. This is a data race.

A data race occurs when:

1. Multiple threads access the same memory.
2. At least one access writes.
3. The accesses are not synchronized.

Correct threaded programs require synchronization.

Zig provides synchronization primitives in the standard library:

| Primitive | Purpose |
|---|---|
| `std.Thread.Mutex` | Protect shared data |
| `std.Thread.RwLock` | Shared and exclusive locking |
| `std.Thread.Condition` | Wait for events |
| `std.atomic` | Atomic memory operations |
| `std.Thread.Semaphore` | Control resource access |

A mutex protects shared state:

```zig
const std = @import("std");

var counter: u32 = 0;
var mutex = std.Thread.Mutex{};

fn increment() void {
    var i: u32 = 0;

    while (i < 100000) : (i += 1) {
        mutex.lock();
        counter += 1;
        mutex.unlock();
    }
}

pub fn main() !void {
    const t1 = try std.Thread.spawn(.{}, increment, .{});
    const t2 = try std.Thread.spawn(.{}, increment, .{});

    t1.join();
    t2.join();

    std.debug.print("{d}\n", .{counter});
}
```

Now the result is predictable.

Only one thread may hold the mutex at a time.

The region between `lock` and `unlock` is called a critical section.

Thread creation is not free.

Creating too many threads can reduce performance because:

- threads consume memory
- context switching costs time
- synchronization adds overhead

Many programs use a fixed number of worker threads instead of creating threads continuously.

Zig does not hide the operating system threading model. A Zig thread maps closely to a native system thread.

This keeps the behavior understandable and predictable.

Exercise 18-1. Write a program that creates four threads. Each thread should print its identifier five times.

Exercise 18-2. Modify the counter example so each thread increments the counter one million times.

Exercise 18-3. Remove the mutex from the synchronized counter program. Run the program several times and compare the results.

Exercise 18-4. Write a function that computes the sum of part of an array. Use two threads to compute the total sum of a larger array.

