std.Thread.Mutex em Zig — Referência e Exemplos

std.Thread.Mutex — Exclusão Mútua

O std.Thread.Mutex é a primitiva fundamental de sincronização do Zig para proteger dados compartilhados entre threads. Ele garante que apenas uma thread por vez possa executar a seção crítica protegida pelo mutex, prevenindo condições de corrida (race conditions). A implementação usa primitivas nativas do sistema operacional (futex no Linux, SRWLock no Windows) para máxima eficiência.

Visão Geral

const std = @import("std");
const Mutex = std.Thread.Mutex;

Estrutura

O Mutex é um tipo leve que não requer alocação:

pub const Mutex = struct {
    pub fn lock(self: *Mutex) void
    pub fn unlock(self: *Mutex) void
    pub fn tryLock(self: *Mutex) bool
};

// Inicialização
var mutex = Mutex{};

Funções Principais

// Adquire o lock (bloqueia se necessário)
pub fn lock(self: *Mutex) void

// Libera o lock
pub fn unlock(self: *Mutex) void

// Tenta adquirir sem bloquear (retorna true se conseguiu)
pub fn tryLock(self: *Mutex) bool

Condition

O Mutex integra-se com std.Thread.Condition para espera condicional:

pub const Condition = struct {
    pub fn wait(self: *Condition, mutex: *Mutex) void
    pub fn signal(self: *Condition) void
    pub fn broadcast(self: *Condition) void
};

Exemplo 1: Contador Thread-Safe

const std = @import("std");

const ContadorSeguro = struct {
    valor: u64 = 0,
    mutex: std.Thread.Mutex = .{},

    pub fn incrementar(self: *ContadorSeguro) void {
        self.mutex.lock();
        defer self.mutex.unlock();
        self.valor += 1;
    }

    pub fn obter(self: *ContadorSeguro) u64 {
        self.mutex.lock();
        defer self.mutex.unlock();
        return self.valor;
    }
};

fn trabalhoThread(contador: *ContadorSeguro) void {
    for (0..10_000) |_| {
        contador.incrementar();
    }
}

pub fn main() !void {
    var contador = ContadorSeguro{};

    const NUM_THREADS = 8;
    var threads: [NUM_THREADS]std.Thread = undefined;

    for (0..NUM_THREADS) |i| {
        threads[i] = try std.Thread.spawn(.{}, trabalhoThread, .{&contador});
    }

    for (threads) |t| {
        t.join();
    }

    const stdout = std.io.getStdOut().writer();
    try stdout.print("Contador final: {d}\n", .{contador.obter()});
    try stdout.print("Esperado:       {d}\n", .{NUM_THREADS * 10_000});
}

Exemplo 2: Fila Thread-Safe com Condition Variable

const std = @import("std");

fn FilaSegura(comptime T: type, comptime N: usize) type {
    return struct {
        buffer: [N]T = undefined,
        inicio: usize = 0,
        fim: usize = 0,
        len: usize = 0,
        mutex: std.Thread.Mutex = .{},
        nao_vazia: std.Thread.Condition = .{},
        nao_cheia: std.Thread.Condition = .{},

        const Self = @This();

        pub fn push(self: *Self, item: T) void {
            self.mutex.lock();
            defer self.mutex.unlock();

            while (self.len == N) {
                self.nao_cheia.wait(&self.mutex);
            }

            self.buffer[self.fim] = item;
            self.fim = (self.fim + 1) % N;
            self.len += 1;

            self.nao_vazia.signal();
        }

        pub fn pop(self: *Self) T {
            self.mutex.lock();
            defer self.mutex.unlock();

            while (self.len == 0) {
                self.nao_vazia.wait(&self.mutex);
            }

            const item = self.buffer[self.inicio];
            self.inicio = (self.inicio + 1) % N;
            self.len -= 1;

            self.nao_cheia.signal();
            return item;
        }
    };
}

var fila = FilaSegura(i32, 16){};

fn produtor() void {
    for (0..20) |i| {
        fila.push(@intCast(i));
        std.debug.print("Produziu: {d}\n", .{i});
    }
}

fn consumidor() void {
    for (0..20) |_| {
        const item = fila.pop();
        std.debug.print("Consumiu: {d}\n", .{item});
    }
}

pub fn main() !void {
    const t_prod = try std.Thread.spawn(.{}, produtor, .{});
    const t_cons = try std.Thread.spawn(.{}, consumidor, .{});

    t_prod.join();
    t_cons.join();

    std.debug.print("Produtor/Consumidor concluído\n", .{});
}

Exemplo 3: Cache Thread-Safe com RwLock

const std = @import("std");

fn CacheSeguro(comptime V: type) type {
    return struct {
        dados: std.StringHashMap(V),
        lock: std.Thread.RwLock = .{},

        const Self = @This();

        pub fn init(allocator: std.mem.Allocator) Self {
            return .{
                .dados = std.StringHashMap(V).init(allocator),
            };
        }

        pub fn deinit(self: *Self) void {
            self.dados.deinit();
        }

        // Leitura — múltiplas threads simultaneamente
        pub fn get(self: *Self, chave: []const u8) ?V {
            self.lock.lockShared();
            defer self.lock.unlockShared();
            return self.dados.get(chave);
        }

        // Escrita — exclusiva
        pub fn put(self: *Self, chave: []const u8, valor: V) !void {
            self.lock.lock();
            defer self.lock.unlock();
            try self.dados.put(chave, valor);
        }

        pub fn count(self: *Self) usize {
            self.lock.lockShared();
            defer self.lock.unlockShared();
            return self.dados.count();
        }
    };
}

var cache: CacheSeguro(i32) = undefined;

fn escritor(id: usize) void {
    var buf: [32]u8 = undefined;
    for (0..10) |i| {
        const chave = std.fmt.bufPrint(&buf, "chave_{d}_{d}", .{ id, i }) catch continue;
        cache.put(chave, @intCast(i * id)) catch continue;
    }
}

fn leitor() void {
    for (0..50) |_| {
        _ = cache.get("chave_0_5");
        std.Thread.yield() catch {};
    }
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    cache = CacheSeguro(i32).init(gpa.allocator());
    defer cache.deinit();

    var threads: [6]std.Thread = undefined;

    // 3 escritores + 3 leitores
    for (0..3) |i| {
        threads[i] = try std.Thread.spawn(.{}, escritor, .{i});
    }
    for (3..6) |i| {
        threads[i] = try std.Thread.spawn(.{}, leitor, .{});
    }

    for (threads) |t| {
        t.join();
    }

    const stdout = std.io.getStdOut().writer();
    try stdout.print("Cache contém {d} entradas\n", .{cache.count()});
}

Padrões Comuns

defer para Garantir unlock

Sempre use defer para garantir que o lock seja liberado:

mutex.lock();
defer mutex.unlock();
// ... seção crítica ...

tryLock para Evitar Bloqueio

if (mutex.tryLock()) {
    defer mutex.unlock();
    // conseguiu o lock
} else {
    // lock ocupado, faz outra coisa
}

Módulos Relacionados

Tutoriais e Receitas Relacionados

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.