Como Usar Timers e Sleep em Zig

Introdução

Timers e sleep são fundamentais para controlar o fluxo temporal de programas: pausar entre operações, implementar polling, criar intervalos periódicos e medir duração de operações. Zig oferece funções precisas para isso em std.time.

Nesta receita, você aprenderá a pausar a execução, medir tempo e criar timers.

Pré-requisitos

Sleep Básico

const std = @import("std");

pub fn main() !void {
    std.debug.print("Iniciando...\n", .{});

    // Pausar por 1 segundo
    std.debug.print("Pausando 1 segundo...\n", .{});
    std.time.sleep(std.time.ns_per_s * 1);

    // Pausar por 500 milissegundos
    std.debug.print("Pausando 500ms...\n", .{});
    std.time.sleep(std.time.ns_per_ms * 500);

    // Pausar por 100 microssegundos
    std.debug.print("Pausando 100us...\n", .{});
    std.time.sleep(std.time.ns_per_us * 100);

    std.debug.print("Concluído!\n", .{});
}

Timer para Medir Duração

const std = @import("std");

pub fn main() !void {
    // Iniciar timer
    var timer = try std.time.Timer.start();

    // Simular trabalho
    std.time.sleep(std.time.ns_per_ms * 250);

    // Ler tempo decorrido
    const elapsed = timer.read();

    std.debug.print("Tempo decorrido: {d} ns\n", .{elapsed});
    std.debug.print("Tempo decorrido: {d:.2} ms\n", .{
        @as(f64, @floatFromInt(elapsed)) / @as(f64, @floatFromInt(std.time.ns_per_ms)),
    });

    // Resetar timer
    timer.reset();

    // Outra medição
    std.time.sleep(std.time.ns_per_ms * 100);
    const elapsed2 = timer.read();

    std.debug.print("Segunda medição: {d:.2} ms\n", .{
        @as(f64, @floatFromInt(elapsed2)) / @as(f64, @floatFromInt(std.time.ns_per_ms)),
    });

    // Lap (intervalo desde o último lap/start)
    timer.reset();
    std.time.sleep(std.time.ns_per_ms * 50);
    const lap1 = timer.lap();
    std.time.sleep(std.time.ns_per_ms * 100);
    const lap2 = timer.lap();

    std.debug.print("Lap 1: {d:.2} ms\n", .{
        @as(f64, @floatFromInt(lap1)) / @as(f64, @floatFromInt(std.time.ns_per_ms)),
    });
    std.debug.print("Lap 2: {d:.2} ms\n", .{
        @as(f64, @floatFromInt(lap2)) / @as(f64, @floatFromInt(std.time.ns_per_ms)),
    });
}

Intervalo Periódico

const std = @import("std");

pub fn main() !void {
    const intervalo_ms: u64 = 500;
    const max_iteracoes: u32 = 5;

    std.debug.print("Executando a cada {d}ms ({d} vezes):\n", .{ intervalo_ms, max_iteracoes });

    var timer = try std.time.Timer.start();

    for (0..max_iteracoes) |i| {
        const tempo_total = timer.read();
        const ms = @as(f64, @floatFromInt(tempo_total)) / std.time.ns_per_ms;
        std.debug.print("  [{d}] t={d:.0}ms - Executando tarefa...\n", .{ i, ms });

        // Simular trabalho (tempo variável)
        std.time.sleep(std.time.ns_per_ms * 50);

        // Sleep pelo restante do intervalo
        const trabalho = timer.lap();
        const sleep_ns = (intervalo_ms * std.time.ns_per_ms) -| trabalho;
        if (sleep_ns > 0) {
            std.time.sleep(sleep_ns);
        }
        _ = timer.lap(); // Reset lap para próxima iteração
    }

    const total = timer.read();
    std.debug.print("\nTempo total: {d:.0}ms\n", .{
        @as(f64, @floatFromInt(total)) / std.time.ns_per_ms,
    });
}

Timeout para Operações

const std = @import("std");

fn operacaoComTimeout(timeout_ms: u64) !bool {
    var timer = try std.time.Timer.start();
    const timeout_ns = timeout_ms * std.time.ns_per_ms;

    // Simular operação que verifica periodicamente
    var tentativas: u32 = 0;
    while (true) {
        tentativas += 1;

        // Verificar timeout
        if (timer.read() >= timeout_ns) {
            std.debug.print("  Timeout após {d} tentativas\n", .{tentativas});
            return false;
        }

        // Simular verificação (ex: poll de recurso)
        if (tentativas == 5) {
            std.debug.print("  Sucesso após {d} tentativas\n", .{tentativas});
            return true;
        }

        std.time.sleep(std.time.ns_per_ms * 100);
    }
}

pub fn main() !void {
    std.debug.print("Teste 1 (timeout curto - 300ms):\n", .{});
    const r1 = try operacaoComTimeout(300);
    std.debug.print("Resultado: {}\n\n", .{r1});

    std.debug.print("Teste 2 (timeout longo - 2000ms):\n", .{});
    const r2 = try operacaoComTimeout(2000);
    std.debug.print("Resultado: {}\n", .{r2});
}

Formatar Duração Legível

const std = @import("std");

fn formatarDuracao(ns: u64) struct { valor: f64, unidade: []const u8 } {
    if (ns < std.time.ns_per_us) {
        return .{ .valor = @floatFromInt(ns), .unidade = "ns" };
    } else if (ns < std.time.ns_per_ms) {
        return .{
            .valor = @as(f64, @floatFromInt(ns)) / std.time.ns_per_us,
            .unidade = "us",
        };
    } else if (ns < std.time.ns_per_s) {
        return .{
            .valor = @as(f64, @floatFromInt(ns)) / std.time.ns_per_ms,
            .unidade = "ms",
        };
    } else {
        return .{
            .valor = @as(f64, @floatFromInt(ns)) / std.time.ns_per_s,
            .unidade = "s",
        };
    }
}

pub fn main() !void {
    const duracoes = [_]u64{
        500,                       // 500 ns
        15_000,                    // 15 us
        3_500_000,                 // 3.5 ms
        1_200_000_000,             // 1.2 s
    };

    for (duracoes) |ns| {
        const f = formatarDuracao(ns);
        std.debug.print("{d} ns = {d:.2} {s}\n", .{ ns, f.valor, f.unidade });
    }
}

Dicas e Boas Práticas

  1. sleep aceita nanossegundos: Use constantes como ns_per_s para legibilidade.

  2. Timer.lap() para intervalos: Mede o tempo desde o último lap() ou start().

  3. Saturação em timeouts: Use -| (subtração saturada) para evitar underflow ao calcular tempo restante.

  4. Sleep não é preciso: A precisão depende do SO. Não use para temporização exata.

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

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