# Condition Variables

### Condition Variables

A mutex protects shared data.

A condition variable allows threads to wait for a change in that data.

The usual pattern is:

1. lock a mutex
2. check a condition
3. sleep until the condition changes
4. wake up and check again

A producer and consumer example shows the idea.

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

const State = struct {
    mutex: std.Thread.Mutex = .{},
    condition: std.Thread.Condition = .{},
    ready: bool = false,
    value: u32 = 0,
};

fn producer(state: *State) void {
    state.mutex.lock();
    defer state.mutex.unlock();

    state.value = 42;
    state.ready = true;

    state.condition.signal();
}

fn consumer(state: *State) void {
    state.mutex.lock();
    defer state.mutex.unlock();

    while (!state.ready) {
        state.condition.wait(&state.mutex);
    }

    std.debug.print("value = {d}\n", .{state.value});
}

pub fn main() !void {
    var state = State{};

    const t1 = try std.Thread.spawn(.{}, producer, .{&state});
    const t2 = try std.Thread.spawn(.{}, consumer, .{&state});

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

The shared state contains:

```zig
mutex: std.Thread.Mutex = .{},
condition: std.Thread.Condition = .{},
ready: bool = false,
value: u32 = 0,
```

The mutex protects the shared data.

The condition variable allows a thread to sleep until another thread signals that the data changed.

The consumer waits here:

```zig
while (!state.ready) {
    state.condition.wait(&state.mutex);
}
```

`wait` does three things:

1. unlocks the mutex
2. puts the thread to sleep
3. locks the mutex again before returning

This is important.

If the mutex stayed locked while waiting, the producer could never acquire it to update the shared state.

The condition is checked in a `while` loop, not an `if` statement.

This is required.

A thread may wake up even though the condition is still false. This is called a spurious wakeup.

Always write:

```zig
while (!condition) {
    wait(...)
}
```

not:

```zig
if (!condition) {
    wait(...)
}
```

The producer signals the condition here:

```zig
state.condition.signal();
```

`signal` wakes one waiting thread.

There is also:

```zig
state.condition.broadcast();
```

which wakes all waiting threads.

Use `signal` when only one thread should continue.

Use `broadcast` when all waiting threads must recheck the condition.

Condition variables are useful for queues.

Here is a small single-item queue:

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

const Queue = struct {
    mutex: std.Thread.Mutex = .{},
    condition: std.Thread.Condition = .{},

    full: bool = false,
    value: u32 = 0,

    fn put(self: *Queue, n: u32) void {
        self.mutex.lock();
        defer self.mutex.unlock();

        while (self.full) {
            self.condition.wait(&self.mutex);
        }

        self.value = n;
        self.full = true;

        self.condition.signal();
    }

    fn get(self: *Queue) u32 {
        self.mutex.lock();
        defer self.mutex.unlock();

        while (!self.full) {
            self.condition.wait(&self.mutex);
        }

        const n = self.value;
        self.full = false;

        self.condition.signal();

        return n;
    }
};

fn producer(queue: *Queue) void {
    var i: u32 = 1;

    while (i <= 5) : (i += 1) {
        queue.put(i);
    }
}

fn consumer(queue: *Queue) void {
    var i: u32 = 0;

    while (i < 5) : (i += 1) {
        const n = queue.get();
        std.debug.print("got {d}\n", .{n});
    }
}

pub fn main() !void {
    var queue = Queue{};

    const t1 = try std.Thread.spawn(.{}, producer, .{&queue});
    const t2 = try std.Thread.spawn(.{}, consumer, .{&queue});

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

The queue has one slot.

If the queue is full, the producer waits.

If the queue is empty, the consumer waits.

The condition variable avoids busy waiting.

Without a condition variable, the consumer might repeatedly check:

```zig
while (!queue.full) {}
```

This wastes CPU time.

A condition variable lets the thread sleep until another thread changes the state.

The mutex and condition variable work together:

- the mutex protects the state
- the condition variable waits for changes in that state

The condition itself is always stored in ordinary shared data:

```zig
full: bool
ready: bool
count > 0
```

The condition variable is only the waiting mechanism.

A condition variable does not remember signals.

This is wrong:

```zig
condition.signal();
```

before another thread begins waiting, if no shared state records the event.

The waiting thread may sleep forever.

Always pair the condition variable with shared state protected by the mutex.

Exercise 18-13. Change the queue size from one item to four items.

Exercise 18-14. Add multiple producer threads.

Exercise 18-15. Add multiple consumer threads.

Exercise 18-16. Replace the busy-wait atomic flag example from the previous section with a condition variable.

