# Condition Variables

### Condition Variables

A condition variable lets one thread sleep until another thread says that something has changed.

It is used with a mutex.

The mutex protects the shared data. The condition variable lets waiting threads wake up when that data may now be ready.

#### The Problem

Suppose one thread produces work, and another thread consumes work.

The consumer should not run forever checking:

```zig
while (queue_is_empty) {
    // keep checking
}
```

That wastes CPU.

A condition variable lets the consumer sleep while the queue is empty. When the producer adds work, it wakes the consumer.

#### Basic Shape

A condition variable in Zig is `std.Thread.Condition`.

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

const QueueState = struct {
    mutex: std.Thread.Mutex = .{},
    condition: std.Thread.Condition = .{},
    has_work: bool = false,
};
```

There are three pieces:

| Field | Purpose |
|---|---|
| `mutex` | Protects the shared state |
| `condition` | Lets threads wait and wake |
| `has_work` | The condition being checked |

The condition variable does not store the condition by itself. The condition is your data, such as `has_work`, `queue.items.len > 0`, or `shutdown == true`.

#### Waiting

A waiting thread locks the mutex, checks the condition, and waits if the condition is false.

```zig
state.mutex.lock();
defer state.mutex.unlock();

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

The important line is:

```zig
state.condition.wait(&state.mutex);
```

This does two things:

1. unlocks the mutex while the thread sleeps
2. locks the mutex again before returning

That matters because the producer needs the same mutex to change the shared state.

#### Signaling

The producer changes the shared state while holding the mutex, then signals the condition variable.

```zig
state.mutex.lock();
defer state.mutex.unlock();

state.has_work = true;
state.condition.signal();
```

`signal` wakes one waiting thread.

If several threads may be waiting, use:

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

`broadcast` wakes all waiting threads.

#### Full Example

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

const State = struct {
    mutex: std.Thread.Mutex = .{},
    condition: std.Thread.Condition = .{},
    has_work: bool = false,
};

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

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

    std.debug.print("worker received work\n", .{});
}

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

    const thread = try std.Thread.spawn(.{}, worker, .{&state});

    std.time.sleep(500 * std.time.ns_per_ms);

    state.mutex.lock();
    state.has_work = true;
    state.condition.signal();
    state.mutex.unlock();

    thread.join();
}
```

The worker starts first and waits.

The main thread sleeps briefly, sets `has_work` to `true`, then signals.

The worker wakes up, checks `has_work`, and continues.

#### Always Wait in a Loop

This is wrong:

```zig
if (!state.has_work) {
    state.condition.wait(&state.mutex);
}
```

Use this instead:

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

A waiting thread can wake up even when the condition is not ready. This is called a spurious wakeup.

Also, another thread may consume the work before this thread gets the mutex again.

The rule is simple:

When a thread wakes up, it must check the condition again.

#### The Condition Is Your Data

The name “condition variable” can be misleading. The condition variable does not know what condition you care about.

This is the condition:

```zig
state.has_work
```

This is only the waiting and waking tool:

```zig
state.condition
```

So this is the real pattern:

```zig
while (!condition_is_true) {
    condition_variable.wait(&mutex);
}
```

The condition must be protected by the same mutex used with `wait`.

#### Producer and Consumer Example

Here is a small one-item queue.

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

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

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

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

    const value = state.item;
    state.has_item = false;

    std.debug.print("consumed {}\n", .{value});
}

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

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

    state.mutex.lock();
    state.item = 42;
    state.has_item = true;
    state.condition.signal();
    state.mutex.unlock();

    thread.join();
}
```

The shared state has two fields:

```zig
has_item: bool = false,
item: u32 = 0,
```

The mutex protects both fields.

The condition variable wakes the consumer when the producer stores an item.

#### Why Signal After Changing State

This is correct:

```zig
state.mutex.lock();
state.has_item = true;
state.condition.signal();
state.mutex.unlock();
```

This is wrong:

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

state.mutex.lock();
state.has_item = true;
state.mutex.unlock();
```

The signal should mean: “the shared state may now satisfy the condition.”

If you signal before changing the state, a waiting thread can wake up, check the condition, find it still false, and go back to sleep.

#### Signal or Broadcast

Use `signal` when one waiting thread can make progress.

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

Use `broadcast` when many waiting threads may need to wake up.

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

Examples:

| Situation | Operation |
|---|---|
| One new queue item | `signal` |
| Shutdown flag changed | `broadcast` |
| One worker slot available | `signal` |
| Configuration changed for all threads | `broadcast` |

A common rule:

If the change can help only one thread, signal. If the change affects all waiting threads, broadcast.

#### Adding Shutdown

Real worker threads need a way to stop.

A condition variable is often used with both “work is ready” and “shutdown requested.”

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

const State = struct {
    mutex: std.Thread.Mutex = .{},
    condition: std.Thread.Condition = .{},
    has_work: bool = false,
    shutdown: bool = false,
};

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

    while (!state.has_work and !state.shutdown) {
        state.condition.wait(&state.mutex);
    }

    if (state.shutdown) {
        std.debug.print("worker shutting down\n", .{});
        return;
    }

    std.debug.print("worker doing work\n", .{});
}
```

The wait condition now checks two facts:

```zig
while (!state.has_work and !state.shutdown)
```

The worker sleeps only while there is no work and no shutdown request.

To stop the worker:

```zig
state.mutex.lock();
state.shutdown = true;
state.condition.broadcast();
state.mutex.unlock();
```

`broadcast` is useful here because all waiting workers should wake up and exit.

#### Do Not Hold the Lock During Long Work

Usually, the worker should take the work while holding the mutex, then release the mutex before doing the expensive part.

Bad:

```zig
state.mutex.lock();
defer state.mutex.unlock();

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

doExpensiveWork();
```

This keeps the mutex locked during the expensive work.

Better:

```zig
state.mutex.lock();

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

const item = state.item;
state.has_work = false;

state.mutex.unlock();

doExpensiveWork(item);
```

The lock protects the queue. It should not protect the whole job.

#### Mental Model

A condition variable is not a message queue. It does not remember every signal as a separate event.

Think of it this way:

The shared data is the truth.

The mutex protects the truth.

The condition variable helps threads sleep until the truth might have changed.

That is why waiting always uses a loop. A wakeup is not a guarantee. It is only a reason to check again.

#### Good Condition Variable Style

A good pattern looks like this:

```zig
mutex.lock();
defer mutex.unlock();

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

// use protected state
```

And the signaling side looks like this:

```zig
mutex.lock();
defer mutex.unlock();

change_shared_state();
condition.signal();
```

For shutdown or global state changes:

```zig
mutex.lock();
defer mutex.unlock();

shutdown = true;
condition.broadcast();
```

Condition variables are useful when a thread should wait without wasting CPU. They are the standard tool for queues, worker pools, producer-consumer systems, and graceful shutdown.

