std.Thread em Zig — Referência e Exemplos

std.Thread — Threads Nativas

O módulo std.Thread fornece acesso a threads nativas do sistema operacional no Zig. Ele permite criar threads, aguardar sua conclusão, e trabalhar com primitivas de sincronização como mutexes e condições. O modelo de threading do Zig é direto e explícito — sem runtime oculto, sem coloração de funções (async/sync) e sem garbage collector interferindo.

Visão Geral

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

Ciclo de Vida de uma Thread

  1. Spawn: Cria e inicia a thread com uma função e argumentos
  2. Execução: A thread roda concorrentemente
  3. Join: O chamador espera a thread terminar e coleta o resultado
  4. Detach: Alternativa ao join — a thread roda independentemente

Funções Principais

Criação e Controle

// Cria e inicia uma nova thread
pub fn spawn(config: SpawnConfig, function: anytype, args: anytype) !Thread

// Aguarda a thread terminar e retorna o resultado
pub fn join(self: Thread) ReturnType

// Desanexa a thread (roda independentemente)
pub fn detach(self: Thread) void

// Retorna o ID da thread atual
pub fn getCurrentId() Id

SpawnConfig

pub const SpawnConfig = struct {
    // Tamanho da stack (null = padrão do SO)
    stack_size: ?usize = null,
};

Exemplo 1: Criação e Join de Threads

const std = @import("std");

fn trabalho(id: usize) void {
    std.debug.print("Thread {d}: iniciando trabalho\n", .{id});

    // Simula trabalho computacional
    var soma: u64 = 0;
    for (0..1_000_000) |i| {
        soma +%= i;
    }

    std.debug.print("Thread {d}: resultado = {d}\n", .{ id, soma });
}

pub fn main() !void {
    const NUM_THREADS = 4;
    var threads: [NUM_THREADS]std.Thread = undefined;

    // Spawn das threads
    for (0..NUM_THREADS) |i| {
        threads[i] = try std.Thread.spawn(.{}, trabalho, .{i});
    }

    std.debug.print("Main: todas as threads iniciadas\n", .{});

    // Join — espera todas terminarem
    for (threads) |t| {
        t.join();
    }

    std.debug.print("Main: todas as threads terminaram\n", .{});
}

Exemplo 2: Threads com Retorno de Valor

const std = @import("std");

fn calcularFatorial(n: u64) u64 {
    var resultado: u64 = 1;
    var i: u64 = 2;
    while (i <= n) : (i += 1) {
        resultado *%= i;
    }
    return resultado;
}

fn threadFatorial(n: u64) u64 {
    std.debug.print("Calculando {d}!...\n", .{n});
    return calcularFatorial(n);
}

pub fn main() !void {
    // Cria threads para cálculos paralelos
    const t1 = try std.Thread.spawn(.{}, threadFatorial, .{@as(u64, 10)});
    const t2 = try std.Thread.spawn(.{}, threadFatorial, .{@as(u64, 15)});
    const t3 = try std.Thread.spawn(.{}, threadFatorial, .{@as(u64, 20)});

    // Join coleta os resultados
    const r1 = t1.join();
    const r2 = t2.join();
    const r3 = t3.join();

    const stdout = std.io.getStdOut().writer();
    try stdout.print("10! = {d}\n", .{r1});
    try stdout.print("15! = {d}\n", .{r2});
    try stdout.print("20! = {d}\n", .{r3});
}

Exemplo 3: Processamento Paralelo de Dados

const std = @import("std");

const Resultado = struct {
    soma: u64,
    min: i32,
    max: i32,
};

fn processarBloco(dados: []const i32) Resultado {
    var soma: u64 = 0;
    var min_val: i32 = std.math.maxInt(i32);
    var max_val: i32 = std.math.minInt(i32);

    for (dados) |v| {
        soma += @as(u64, @intCast(@abs(v)));
        if (v < min_val) min_val = v;
        if (v > max_val) max_val = v;
    }

    return .{ .soma = soma, .min = min_val, .max = max_val };
}

pub fn main() !void {
    // Dados de exemplo
    var dados: [10000]i32 = undefined;
    for (&dados, 0..) |*d, i| {
        d.* = @intCast(@as(i64, @intCast(i)) - 5000);
    }

    const NUM_THREADS = 4;
    const bloco_tam = dados.len / NUM_THREADS;

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

    // Divide o trabalho
    for (0..NUM_THREADS) |i| {
        const inicio = i * bloco_tam;
        const fim = if (i == NUM_THREADS - 1) dados.len else (i + 1) * bloco_tam;
        const bloco = dados[inicio..fim];
        threads[i] = try std.Thread.spawn(.{}, processarBloco, .{bloco});
    }

    // Coleta resultados
    var soma_total: u64 = 0;
    var min_global: i32 = std.math.maxInt(i32);
    var max_global: i32 = std.math.minInt(i32);

    for (threads) |t| {
        const r = t.join();
        soma_total += r.soma;
        if (r.min < min_global) min_global = r.min;
        if (r.max > max_global) max_global = r.max;
    }

    const stdout = std.io.getStdOut().writer();
    try stdout.print("Resultados ({d} threads):\n", .{NUM_THREADS});
    try stdout.print("  Soma absoluta: {d}\n", .{soma_total});
    try stdout.print("  Mínimo: {d}\n", .{min_global});
    try stdout.print("  Máximo: {d}\n", .{max_global});
}

Padrões Comuns

Thread com Estado Compartilhado

Para compartilhar dados entre threads, use ponteiros e sincronização:

var contador = std.atomic.Value(u64).init(0);

fn incrementar() void {
    for (0..1000) |_| {
        _ = contador.fetchAdd(1, .seq_cst);
    }
}

Detach vs Join

  • join(): Bloqueia até a thread terminar; pode capturar o valor de retorno
  • detach(): A thread continua em background; não é possível obter o resultado

Módulos Relacionados

Tutoriais e Receitas Relacionados

Continue aprendendo Zig

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