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.