<h2>Entendendo Concorrência e a Necessidade de Sincronização</h2>
<p>A programação concorrente em Go permite que múltiplas goroutines executem simultaneamente, compartilhando recursos como variáveis, estruturas de dados e conexões com banco de dados. Quando várias goroutines acessam o mesmo recurso ao mesmo tempo, surgem problemas graves: race conditions, corrupção de dados e comportamentos impredíveis. A exclusão mútua (mutex) é o mecanismo fundamental para garantir que apenas uma goroutine acesse um recurso crítico por vez, preservando a integridade dos dados.</p>
<p>Considere um exemplo real: você tem um contador compartilhado que múltiplas goroutines incrementam simultaneamente. Sem sincronização, duas goroutines poderiam ler o mesmo valor, incrementar e escrever de volta, causando perda de incrementos. O Go fornece dois tipos de mutex na package <code>sync</code>: o <code>Mutex</code> (mutex exclusivo) e o <code>RWMutex</code> (mutex leitor-escritor). Ambos resolvem o problema, mas de formas diferentes, com trade-offs distintos.</p>
<h2>Mutex: Exclusão Mútua Simples</h2>
<p>O <code>sync.Mutex</code> é a forma mais direta de proteger um recurso crítico. Ele funciona como uma porta com uma chave: apenas a goroutine que possui a chave pode entrar na seção crítica. Quando você chama <code>Lock()</code>, a goroutine adquire o mutex; quando chama <code>Unlock()</code>, libera-o. Se outra goroutine tentar fazer <code>Lock()</code> enquanto o mutex está ocupado, ela bloqueia até que o mutex seja liberado.</p>
<h3>Estrutura Básica e Funcionamento</h3>
<p>A forma padrão de usar Mutex é envolver um recurso compartilhado com a proteção:</p>
<pre><code class="language-go">package main
import (
"fmt"
"sync"
)
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}
func main() {
counter := &Counter{}
var wg sync.WaitGroup
// 100 goroutines incrementando o contador
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 1000; j++ {
counter.Increment()
}
}()
}
wg.Wait()
fmt.Printf("Valor final: %d (esperado: 100000)\n", counter.Value())
}</code></pre>
<p>Neste exemplo, o campo <code>mu</code> protege o campo <code>value</code>. O padrão <code>Lock()</code> seguido por <code>defer Unlock()</code> garante que o mutex seja sempre liberado, mesmo se um pânico ocorrer. Sem essa proteção, o resultado final seria impredívelmente menor que 100000 devido à race condition.</p>
<h3>Deadlock e Boas Práticas</h3>
<p>Um risco comum ao usar Mutex é o deadlock: quando uma goroutine tenta adquirir um mutex que ela mesma já possui (em Go, isso causará um pânico). Outro cenário é quando goroutines A e B esperam uma pela outra indefinidamente. A melhor prática é manter seções críticas pequenas e evitar chamar funções que também tentam adquirir o mesmo mutex.</p>
<pre><code class="language-go">package main
import (
"sync"
)
type BankAccount struct {
mu sync.Mutex
balance float64
}
func (ba BankAccount) Transfer(other BankAccount, amount float64) {
// ⚠️ PERIGO: se outro Transfer chamar Transfer na ordem inversa, deadlock!
ba.mu.Lock()
defer ba.mu.Unlock()
other.mu.Lock()
defer other.mu.Unlock()
ba.balance -= amount
other.balance += amount
}
// ✓ Solução: usar ordem consistente
func (ba BankAccount) TransferSafe(other BankAccount, amount float64) {
// Sempre fazer lock na conta com ID menor primeiro
first, second := ba, other
if uintptr(unsafe.Pointer(ba)) > uintptr(unsafe.Pointer(other)) {
first, second = other, ba
}
first.mu.Lock()
defer first.mu.Unlock()
second.mu.Lock()
defer second.mu.Unlock()
ba.balance -= amount
other.balance += amount
}</code></pre>
<h2>RWMutex: Otimizando Leituras Frequentes</h2>
<p>O <code>sync.RWMutex</code> é um mutex leitor-escritor que permite múltiplos leitores simultâneos, mas apenas um escritor exclusivo. Isso é ideal quando seu padrão de acesso é muito mais leitura do que escrita. Diferentemente do <code>Mutex</code>, que serializa tudo, o <code>RWMutex</code> permite paralelismo quando não há modificações em progresso.</p>
<h3>Quando Usar RWMutex</h3>
<p>RWMutex oferece três operações: <code>RLock()</code> para leitura compartilhada, <code>RUnlock()</code> para liberar leitura, e <code>Lock()</code>/<code>Unlock()</code> para escrita exclusiva. A regra é simples: se você está apenas lendo, use <code>RLock()</code>; se está modificando, use <code>Lock()</code>.</p>
<pre><code class="language-go">package main
import (
"fmt"
"sync"
"time"
)
type Config struct {
mu sync.RWMutex
values map[string]string
}
func (c *Config) Get(key string) string {
c.mu.RLock()
defer c.mu.RUnlock()
return c.values[key]
}
func (c *Config) Set(key, value string) {
c.mu.Lock()
defer c.mu.Unlock()
c.values[key] = value
}
func main() {
config := &Config{values: make(map[string]string)}
config.Set("database", "localhost:5432")
var wg sync.WaitGroup
// 50 goroutines lendo
for i := 0; i < 50; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < 100; j++ {
_ = config.Get("database")
time.Sleep(time.Microsecond)
}
}(i)
}
// 2 goroutines escrevendo ocasionalmente
for i := 0; i < 2; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < 10; j++ {
time.Sleep(10 * time.Millisecond)
config.Set("database", fmt.Sprintf("server%d", id))
}
}(i)
}
wg.Wait()
fmt.Println("Teste concluído com sucesso")
}</code></pre>
<p>Neste cenário, 50 leitores podem acessar <code>Get()</code> simultaneamente enquanto nenhum escritor está ativo. Quando um escritor chama <code>Set()</code>, todos os novos <code>RLock()</code> esperam e os leitores existentes terminam antes do escritor prosseguir. Isso oferece muito mais throughput que um <code>Mutex</code> simples para este padrão de acesso.</p>
<h3>Armadilhas do RWMutex</h3>
<p>O <code>RWMutex</code> tem overhead maior que o <code>Mutex</code> simples. Se sua workload é principalmente escrita ou leitura/escrita equilibrada, um <code>Mutex</code> simples será mais rápido. Além disso, o <code>RWMutex</code> pode provocar "starvation" de escritores se houver um fluxo constante de leitores.</p>
<pre><code class="language-go">package main
import (
"sync"
"testing"
)
// Demonstra quando Mutex é melhor que RWMutex
func BenchmarkMutex(b *testing.B) {
var mu sync.Mutex
var value int
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
mu.Lock()
value++
mu.Unlock()
}
})
}
func BenchmarkRWMutex(b *testing.B) {
var mu sync.RWMutex
var value int
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
mu.Lock()
value++
mu.Unlock()
}
})
}
// Para aplicações com padrão read-heavy, RWMutex vence
func BenchmarkMutexReadHeavy(b *testing.B) {
var mu sync.Mutex
var value int
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
mu.Lock()
_ = value
mu.Unlock()
}
})
}
func BenchmarkRWMutexReadHeavy(b *testing.B) {
var mu sync.RWMutex
var value int
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
mu.RLock()
_ = value
mu.RUnlock()
}
})
}</code></pre>
<h2>Padrões Avançados e Erros Comuns</h2>
<p>Além do uso básico, existem padrões que aumentam a robustez do código concorrente. Um padrão fundamental é separar a estrutura de dados da lógica que a protege, garantindo que o mutex seja sempre usado quando necessário.</p>
<h3>Padrão de Encapsulamento</h3>
<p>A melhor prática é tornar o recurso privado (com letra minúscula) e expor apenas métodos sincronizados:</p>
<pre><code class="language-go">package main
import (
"sync"
)
type SafeMap struct {
mu sync.RWMutex
items map[string]interface{}
}
func NewSafeMap() *SafeMap {
return &SafeMap{items: make(map[string]interface{})}
}
func (sm *SafeMap) Set(key string, value interface{}) {
sm.mu.Lock()
defer sm.mu.Unlock()
sm.items[key] = value
}
func (sm *SafeMap) Get(key string) (interface{}, bool) {
sm.mu.RLock()
defer sm.mu.RUnlock()
val, ok := sm.items[key]
return val, ok
}
func (sm *SafeMap) Delete(key string) {
sm.mu.Lock()
defer sm.mu.Unlock()
delete(sm.items, key)
}
func (sm *SafeMap) Len() int {
sm.mu.RLock()
defer sm.mu.RUnlock()
return len(sm.items)
}
func main() {
sm := NewSafeMap()
sm.Set("user:1", "Alice")
sm.Set("user:2", "Bob")
if val, ok := sm.Get("user:1"); ok {
println(val.(string)) // Alice
}
println(sm.Len()) // 2
}</code></pre>
<h3>Evitar Erros Comuns</h3>
<p>O erro mais frequente é esquecer de chamar <code>Unlock()</code> ou esquecer o <code>Lock()</code> em uma função que deveria ser sincronizada. Use <code>defer</code> sempre que possível. Outro erro é tentar serializar o próprio mutex ou passar goroutines com comportamento impredível.</p>
<pre><code class="language-go">package main
import (
"sync"
)
// ❌ ERRADO: deadlock potencial
func wrongApproach() {
var mu sync.Mutex
go func() {
mu.Lock()
println("Goroutine 1")
// ... se tentar chamar outra função que faz Lock(), deadlock
}()
go func() {
mu.Lock()
println("Goroutine 2")
mu.Unlock()
}()
}
// ✓ CORRETO: usar canais ou passar o mutex para funções que precisam
func correctApproach() {
var mu sync.Mutex
done := make(chan bool, 2)
go func() {
mu.Lock()
defer mu.Unlock()
println("Goroutine 1")
done <- true
}()
go func() {
mu.Lock()
defer mu.Unlock()
println("Goroutine 2")
done <- true
}()
<-done
<-done
}</code></pre>
<h2>Conclusão</h2>
<p>Aprendemos que <strong>exclusão mútua explícita via <code>sync.Mutex</code> e <code>sync.RWMutex</code> é essencial para programação concorrente segura em Go</strong>. O <code>Mutex</code> fornece proteção simples e adequada para a maioria dos casos, enquanto o <code>RWMutex</code> otimiza cenários read-heavy, permitindo leitores simultâneos. O segundo ponto crítico é que <strong>deadlocks e race conditions são evitáveis através de disciplina: use <code>defer Unlock()</code>, mantenha seções críticas pequenas, estabeleça uma ordem consistente para múltiplos locks, e encapsule recursos compartilhados em tipos com métodos sincronizados</strong>. Por fim, <strong>sempre meça e perfil seu código antes de escolher entre Mutex e RWMutex</strong>, pois o overhead do RWMutex só compensa em padrões verdadeiramente read-heavy.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://pkg.go.dev/sync" target="_blank" rel="noopener noreferrer">Go sync Package - Official Documentation</a></li>
<li><a href="https://golang.org/doc/effective_go#concurrency" target="_blank" rel="noopener noreferrer">Effective Go - Concurrency</a></li>
<li><a href="https://golang.org/ref/mem" target="_blank" rel="noopener noreferrer">The Go Memory Model</a></li>
<li><a href="https://go.dev/blog/pipelines" target="_blank" rel="noopener noreferrer">Go Concurrency Patterns</a></li>
<li><a href="https://www.oreilly.com/library/view/concurrency-in-go/9781491941294/" target="_blank" rel="noopener noreferrer">Concurrency in Go - Katherine Cox-Buday (Book)</a></li>
</ul>
<p><!-- FIM --></p>