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
- std.Thread — Criação e gerenciamento de threads
- std.Thread.Pool — Pool de threads
- std.atomic — Operações atômicas (lock-free)