---
title: "Como Usar Operações Atômicas em Zig"
url: "https://ziglang.com.br/receitas/como-usar-opera%C3%A7%C3%B5es-at%C3%B4micas-em-zig/"
markdown_url: "https://ziglang.com.br/receitas/como-usar-opera%C3%A7%C3%B5es-at%C3%B4micas-em-zig.MD"
description: "Aprenda a usar operações atômicas em Zig com std.atomic para sincronização lock-free entre threads, incluindo load, store, fetchAdd e compareExchange."
date: "2026-02-21"
author: "Zig Brasil"
---

# Como Usar Operações Atômicas em Zig

Aprenda a usar operações atômicas em Zig com std.atomic para sincronização lock-free entre threads, incluindo load, store, fetchAdd e compareExchange.


## Introdução

Operações atômicas são instruções de CPU que executam de forma indivisível, sem possibilidade de interrupção por outra thread. Elas permitem sincronização entre threads sem o overhead de mutexes, sendo ideais para contadores, flags e estruturas lock-free.

Em Zig, `std.atomic.Value` fornece uma API segura para operações atômicas.

## Pré-requisitos

- Zig instalado (versão 0.13+). Veja o [guia de instalação](/tutoriais/como-instalar-zig/)
- Conhecimento de [threads](/receitas/zig-criar-thread/) e [mutex](/receitas/zig-mutex-exemplo/)

## Contador Atômico

O uso mais comum: um contador seguro entre threads sem mutex:

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

pub fn main() !void {
    var contador = std.atomic.Value(u64).init(0);

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

    for (&threads) |*t| {
        t.* = try std.Thread.spawn(.{}, struct {
            fn work(c: *std.atomic.Value(u64)) void {
                for (0..10000) |_| {
                    _ = c.fetchAdd(1, .seq_cst);
                }
            }
        }.work, .{&contador});
    }

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

    const total = contador.load(.seq_cst);
    std.debug.print("Total: {d} (esperado: {d})\n", .{ total, num_threads * 10000 });
}
```

### Saída esperada

```
Total: 80000 (esperado: 80000)
```

## Operações Atômicas Disponíveis

O Zig oferece diversas operações atômicas:

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

pub fn main() void {
    var valor = std.atomic.Value(i32).init(10);

    // Load: ler o valor atual
    const atual = valor.load(.seq_cst);
    std.debug.print("Valor atual: {d}\n", .{atual});

    // Store: definir um novo valor
    valor.store(42, .seq_cst);
    std.debug.print("Após store(42): {d}\n", .{valor.load(.seq_cst)});

    // fetchAdd: somar e retornar o valor anterior
    const anterior = valor.fetchAdd(8, .seq_cst);
    std.debug.print("fetchAdd(8): anterior={d}, novo={d}\n", .{
        anterior,
        valor.load(.seq_cst),
    });

    // fetchSub: subtrair e retornar o valor anterior
    const ant_sub = valor.fetchSub(10, .seq_cst);
    std.debug.print("fetchSub(10): anterior={d}, novo={d}\n", .{
        ant_sub,
        valor.load(.seq_cst),
    });

    // fetchAnd: AND bit a bit
    valor.store(0xFF, .seq_cst);
    _ = valor.fetchAnd(0x0F, .seq_cst);
    std.debug.print("fetchAnd(0x0F) de 0xFF: 0x{X}\n", .{
        @as(u32, @intCast(valor.load(.seq_cst))),
    });

    // fetchOr: OR bit a bit
    valor.store(0x0F, .seq_cst);
    _ = valor.fetchOr(0xF0, .seq_cst);
    std.debug.print("fetchOr(0xF0) de 0x0F: 0x{X}\n", .{
        @as(u32, @intCast(valor.load(.seq_cst))),
    });
}
```

### Saída esperada

```
Valor atual: 10
Após store(42): 42
fetchAdd(8): anterior=42, novo=50
fetchSub(10): anterior=50, novo=40
fetchAnd(0x0F) de 0xFF: 0xF
fetchOr(0xF0) de 0x0F: 0xFF
```

## Compare and Exchange

A operação mais poderosa: atualiza o valor apenas se ele for o esperado:

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

const SpinLock = struct {
    locked: std.atomic.Value(u32) = std.atomic.Value(u32).init(0),

    pub fn lock(self: *SpinLock) void {
        while (self.locked.cmpxchgWeak(0, 1, .acquire, .monotonic) != null) {
            // Spin: esperar até conseguir o lock
            std.atomic.spinLoopHint();
        }
    }

    pub fn unlock(self: *SpinLock) void {
        self.locked.store(0, .release);
    }
};

pub fn main() !void {
    var spin = SpinLock{};
    var dados: u64 = 0;

    var threads: [4]std.Thread = undefined;
    for (&threads) |*t| {
        t.* = try std.Thread.spawn(.{}, struct {
            fn work(s: *SpinLock, d: *u64) void {
                for (0..10000) |_| {
                    s.lock();
                    d.* += 1;
                    s.unlock();
                }
            }
        }.work, .{ &spin, &dados });
    }

    for (&threads) |t| t.join();

    std.debug.print("Dados: {d} (esperado: 40000)\n", .{dados});
}
```

## Flag Atômica para Sinalização

Use uma flag atômica para sinalizar entre threads:

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

const Flag = struct {
    valor: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),

    pub fn set(self: *Flag) void {
        self.valor.store(true, .release);
    }

    pub fn isSet(self: *Flag) bool {
        return self.valor.load(.acquire);
    }

    pub fn reset(self: *Flag) void {
        self.valor.store(false, .release);
    }
};

fn produtor(flag: *Flag) void {
    std.debug.print("Produtor: preparando dados...\n", .{});
    std.time.sleep(std.time.ns_per_s);

    std.debug.print("Produtor: dados prontos! Sinalizando.\n", .{});
    flag.set();
}

fn consumidor(flag: *Flag) void {
    std.debug.print("Consumidor: esperando dados...\n", .{});

    while (!flag.isSet()) {
        std.atomic.spinLoopHint();
    }

    std.debug.print("Consumidor: sinal recebido! Processando dados.\n", .{});
}

pub fn main() !void {
    var flag = Flag{};

    const t1 = try std.Thread.spawn(.{}, consumidor, .{&flag});
    const t2 = try std.Thread.spawn(.{}, produtor, .{&flag});

    t1.join();
    t2.join();
}
```

## Estatísticas Atômicas

Colete estatísticas de forma thread-safe sem mutex:

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

const Estatisticas = struct {
    requisicoes_total: std.atomic.Value(u64) = std.atomic.Value(u64).init(0),
    requisicoes_ok: std.atomic.Value(u64) = std.atomic.Value(u64).init(0),
    requisicoes_erro: std.atomic.Value(u64) = std.atomic.Value(u64).init(0),
    bytes_processados: std.atomic.Value(u64) = std.atomic.Value(u64).init(0),

    pub fn registrarSucesso(self: *Estatisticas, bytes: u64) void {
        _ = self.requisicoes_total.fetchAdd(1, .monotonic);
        _ = self.requisicoes_ok.fetchAdd(1, .monotonic);
        _ = self.bytes_processados.fetchAdd(bytes, .monotonic);
    }

    pub fn registrarErro(self: *Estatisticas) void {
        _ = self.requisicoes_total.fetchAdd(1, .monotonic);
        _ = self.requisicoes_erro.fetchAdd(1, .monotonic);
    }

    pub fn relatorio(self: *Estatisticas) void {
        const total = self.requisicoes_total.load(.seq_cst);
        const ok = self.requisicoes_ok.load(.seq_cst);
        const erros = self.requisicoes_erro.load(.seq_cst);
        const bytes = self.bytes_processados.load(.seq_cst);

        std.debug.print("=== Relatório ===\n", .{});
        std.debug.print("Total: {d}\n", .{total});
        std.debug.print("Sucesso: {d}\n", .{ok});
        std.debug.print("Erros: {d}\n", .{erros});
        std.debug.print("Bytes: {d}\n", .{bytes});
        if (total > 0) {
            std.debug.print("Taxa de erro: {d:.1}%\n", .{
                @as(f64, @floatFromInt(erros)) / @as(f64, @floatFromInt(total)) * 100.0,
            });
        }
    }
};

fn simularWorker(stats: *Estatisticas, id: u32) void {
    var prng = std.Random.DefaultPrng.init(id);
    const rand = prng.random();

    for (0..1000) |_| {
        if (rand.intRangeAtMost(u32, 1, 100) <= 95) {
            stats.registrarSucesso(rand.intRangeAtMost(u64, 100, 5000));
        } else {
            stats.registrarErro();
        }
    }
}

pub fn main() !void {
    var stats = Estatisticas{};

    var threads: [8]std.Thread = undefined;
    for (&threads, 0..) |*t, i| {
        t.* = try std.Thread.spawn(.{}, simularWorker, .{ &stats, @as(u32, @intCast(i)) });
    }

    for (&threads) |t| t.join();

    stats.relatorio();
}
```

## Ordens de Memória

As ordens de memória controlam como operações atômicas são visíveis entre threads:

| Ordem | Descrição | Uso |
|---|---|---|
| `.monotonic` | Sem garantias de ordenação | Contadores, estatísticas |
| `.acquire` | Operações após o load não são reordenadas antes dele | Ler flags/locks |
| `.release` | Operações antes do store não são reordenadas depois dele | Escrever flags/locks |
| `.seq_cst` | Ordem total sequencial | Quando em dúvida |

## Dicas e Boas Práticas

1. **Use `.seq_cst` quando em dúvida**: É a ordem mais segura, embora possa ter pequeno custo de performance.

2. **Atômicos para dados simples**: Para estruturas complexas, use [mutex](/receitas/zig-mutex-exemplo/).

3. **spinLoopHint**: Use `std.atomic.spinLoopHint()` em loops de espera ativa para economizar energia da CPU.

4. **Tamanho importa**: Operações atômicas são mais eficientes para tipos de até 8 bytes (64 bits).

## Receitas Relacionadas

- [Como criar threads em Zig](/receitas/zig-criar-thread/) - Fundamentos
- [Como usar Mutex em Zig](/receitas/zig-mutex-exemplo/) - Alternativa com lock
- [Como usar thread pool em Zig](/receitas/zig-thread-pool/) - Pool de threads
- [Como usar canais de comunicação em Zig](/receitas/zig-canal-comunicacao/) - Coordenação

## Tutoriais Relacionados

- [Concorrência em Zig](/tutoriais/concorrencia-em-zig/)
- [Segurança de Memória em Zig](/tutoriais/zig-seguranca-memoria/)
