Concorrência e Multithreading em Go: do A ao Z

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Concorrência e Multithreading em Go: do A ao Z



Go (Golang) foi projetado pensando nas cargas computacionais modernas. Na era dos processadores multi-core e serviços em nuvem, saber trabalhar eficientemente com concorrência tornou-se uma habilidade obrigatória para desenvolvedores backend. Neste artigo, vamos analisar como o Go resolve problemas de execução paralela de código, como as goroutines diferem das threads do SO e como usar corretamente canais e mutexes.



1. Goroutines: threads leves



Uma goroutine é uma função ou método executado concorrentemente com outras goroutines. Ao contrário das threads do sistema operacional, as goroutines têm sobrecarga mínima: sua pilha começa com 2-4 KB e pode crescer dinamicamente. Iniciar uma goroutine é incrivelmente simples — basta adicionar a palavra-chave go antes da chamada da função.



package main

import ( "fmt" "time")

func say(text string) { for i := 0; i < 3; i++ { fmt.Println(text) time.Sleep(100 * time.Millisecond) }}

func main() { go say("Hello from goroutine") say("Hello from main") // Dá tempo para a goroutine terminar time.Sleep(500 * time.Millisecond)}


Observe: o programa termina imediatamente após a execução da função main(), sem esperar a conclusão das goroutines iniciadas. Para sincronização, usamos time.Sleep (em projetos reais, usa-se sync.WaitGroup ou canais).



2. Canais: transferência segura de dados



Canais (channels) são "tubos" tipados através dos quais as goroutines trocam dados. Eles resolvem duas tarefas principais: sincronização de execução e transferência de valores. Os canais podem ser bufferizados ou não bufferizados.



Canal não bufferizado

A operação de envio bloqueia a goroutine até que outra goroutine realize a leitura. Isso garante sincronização "em tempo real".



package main

import "fmt"

func main() { ch := make(chan string)

go func() { ch <- "Olá da goroutine!" }()

msg := <-ch fmt.Println(msg)}


Canal bufferizado

Um canal com buffer permite enviar vários valores sem leitura imediata, até que o buffer esteja cheio.



package main

import "fmt"

func main() { ch := make(chan int, 2) ch <- 10 ch <- 20 // ch <- 30 // deadlock! buffer cheio

fmt.Println(<-ch) // 10 fmt.Println(<-ch) // 20}


Range e close

Fechar um canal sinaliza aos receptores que não haverá mais dados. O loop for range termina automaticamente quando o canal é fechado.



package main

import "fmt"

func producer(ch chan int) { for i := 1; i <= 5; i++ { ch <- i } close(ch)}

func main() { ch := make(chan int) go producer(ch)

for val := range ch { fmt.Println("Recebido:", val) }}


3. Sincronização e mutexes



Apesar do poder dos canais, às vezes é necessário acesso direto a dados compartilhados. O pacote sync fornece mutexes (sync.Mutex) e mutexes de leitura (sync.RWMutex) para proteger seções críticas.



package main

import ( "fmt" "sync")

var counter = 0var mu sync.Mutex

func increment(wg *sync.WaitGroup) { defer wg.Done() mu.Lock() counter++ mu.Unlock()}

func main() { var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go increment(&wg) } wg.Wait() fmt.Println("Contador final:", counter)}


Aqui, sync.WaitGroup aguarda a conclusão de todas as goroutines, e o mutex previne condições de corrida (data race). Sem o mutex, o resultado seria imprevisível.



4. Padrões de concorrência

Blogs

Book Recommendations