Sobes.tech
Middle+

O que são operações atômicas, quais os seus tipos e quando e como é melhor usá-las?

sobes.tech IA

Resposta da IA

As operações atômicas são operações sobre a memória cuja execução garante que são concluídas totalmente ou não começam de todo, e não podem ser interrompidas por outras operações concorrentes. São indivisíveis e seguras para threads ao nível de hardware.

Em Go, o pacote sync/atomic fornece um conjunto de funções para realizar operações atômicas:

Função Descrição
AddInt32 Adiciona de forma atômica um valor a int32.
AddInt64 Adiciona de forma atômica um valor a int64.
AddUint32 Adiciona de forma atômica um valor a uint32.
AddUint64 Adiciona de forma atômica um valor a uint64.
AddUintptr Adiciona de forma atômica um valor a uintptr.
CompareAndSwapInt32 Compara e troca (CAS) int32 de forma atômica.
CompareAndSwapInt64 Compara e troca (CAS) int64 de forma atômica.
CompareAndSwapUint32 Compara e troca (CAS) uint32.
CompareAndSwapUint64 Compara e troca (CAS) uint64.
CompareAndSwapUintptr Compara e troca (CAS) uintptr.
CompareAndSwapPointer Compara e troca (CAS) unsafe.Pointer.
LoadInt32 Carrega de forma atômica o valor de int32.
LoadInt64 Carrega de forma atômica o valor de int64.
LoadUint32 Carrega de forma atômica o valor de uint32.
LoadUint64 Carrega de forma atômica o valor de uint64.
LoadUintptr Carrega de forma atômica o valor de uintptr.
LoadPointer Carrega de forma atômica o valor de unsafe.Pointer.
StoreInt32 Armazena de forma atômica o valor de int32.
StoreInt64 Armazena de forma atômica o valor de int64.
StoreUint32 Armazena de forma atômica o valor de uint32.
StoreUint64 Armazena de forma atômica o valor de uint64.
StoreUintptr Armazena de forma atômica o valor de uintptr.
StorePointer Armazena de forma atômica o valor de unsafe.Pointer.
SwapInt32 Troca de forma atômica os valores de int32.
SwapInt64 Troca de forma atômica os valores de int64.
SwapUint32 Troca de forma atômica os valores de uint32.
SwapUint64 Troca de forma atômica os valores de uint64.
SwapUintptr Troca de forma atômica os valores de uintptr.
SwapPointer Troca de forma atômica os valores de unsafe.Pointer.

É melhor usar operações atômicas em casos onde é necessária uma sincronização simples e de baixo nível para acesso a variáveis comuns, como contadores ou flags, sem usar mutexes. Elas proporcionam custos mínimos e melhor desempenho em tais cenários.

Exemplo de uso de um contador atômico:

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
	var counter int64
	var wg sync.WaitGroup

	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			// Incrementa o contador de forma atômica
			atomic.AddInt64(&counter, 1)
			wg.Done()
		}()
	}

	wg.Wait()
	// Carrega de forma atômica o valor do contador
	fmt.Println("Contador final:", atomic.LoadInt64(&counter))
}

Exemplo de uso de CompareAndSwap:

package main

import (
	"fmt"
	"sync/atomic"
)

func main() {
	var value int32 = 10

	// Tentativa de trocar o valor se atualmente for 10, para 20
	swapped := atomic.CompareAndSwapInt32(&value, 10, 20)
	fmt.Println("Trocado:", swapped) // true
	fmt.Println("Valor:", value)   // 20

	// Tentativa de trocar o valor se atualmente for 10, para 30
	swapped = atomic.CompareAndSwapInt32(&value, 10, 30)
	fmt.Println("Trocado:", swapped) // false
	fmt.Println("Valor:", value)   // 20
}

É importante lembrar que operações atômicas funcionam com tipos de dados primitivos (inteiros, ponteiros). Para sincronizar o acesso a estruturas de dados mais complexas ou realizar operações bloqueantes, ainda são necessários mutexes ou outros mecanismos de sincronização. Uso excessivo ou incorreto de operações atômicas pode levar a erros difíceis de detectar ou a uma redução de desempenho.