Bem-vindo ao Blog da DMarkInfo

Um espaço exclusivo para compartilhar conteúdos e novidades sobre Tecnologia da Informação. Aqui você encontra artigos, tutoriais e análises para se manter atualizado no mundo da TI.

Concorrência em Go: Uma Abordagem Simples e Poderosa

Postado por Eduardo Marques em 12/09/2025
Concorrência em Go: Uma Abordagem Simples e Poderosa

Go, ou Golang, como é popularmente conhecido, foi projetado com a concorrência em mente. Diferente de outras linguagens que a tratam como um recurso extra, o Go a integra em seu núcleo, oferecendo uma abordagem simples, mas extremamente poderosa, que evita a complexidade de threads e callbacks. A filosofia do Go pode ser resumida em seu slogan: "Don't communicate by sharing memory, share memory by communicating." (Não se comunique compartilhando memória, compartilhe memória comunicando-se).
 

Goroutines: As "Threads Leves" do Go

No coração da concorrência em Go estão as goroutines. Pense nelas como threads muito leves, gerenciadas pelo próprio runtime do Go, e não pelo sistema operacional. Elas são incrivelmente eficientes: uma goroutine consome apenas alguns kilobytes de memória no início, expandindo-se conforme necessário. Isso permite que um programa Go rode dezenas, centenas ou até milhões de goroutines simultaneamente sem sobrecarregar o sistema.

Para criar uma goroutine, basta usar a palavra-chave go antes de uma chamada de função.

package main

import (
    "fmt"
    "time"
)

func saudacao(nome string) {
    time.Sleep(2 * time.Second) // Simula uma tarefa demorada
    fmt.Printf("Olá, %s!\n", nome)
}

func main() {
    go saudacao("Mundo")
    fmt.Println("Execução principal continua...")
    time.Sleep(3 * time.Second) // Aguarda a goroutine terminar
}

 

Neste exemplo, a função saudacao é executada em uma nova goroutine. A execução do programa principal continua imediatamente, sem esperar que a saudação seja impressa. O time.Sleep no main é necessário para que o programa não termine antes que a goroutine tenha a chance de completar sua tarefa.
 

Channels: A Comunicação Segura

Se as goroutines são os trabalhadores, os channels (canais) são os "caminhos" que permitem que eles se comuniquem e sincronizem de forma segura. Um channel é um tipo de dado que permite que goroutines enviem e recebam valores umas das outras. Eles são a forma preferida e mais segura de compartilhar dados, seguindo a filosofia de "compartilhar memória comunicando".

A criação de um channel é feita com a função make:

 

meuCanal := make(chan int) // Cria um channel para valores inteiros

A operação de envio de um valor é feita com o operador <- (seta para a esquerda) e a de recebimento com o mesmo operador, mas com a seta apontando para a variável que receberá o valor.

 

// Envio
meuCanal <- 10

// Recebimento
valor := <- meuCanal

Por padrão, as operações em canais são sincronizadas. Uma operação de envio em um channel só prossegue quando outra goroutine está pronta para receber o valor, e vice-versa. Isso garante que as goroutines "conversem" de forma coordenada, evitando condições de corrida (race conditions) comuns em programação com threads tradicionais.

Veja um exemplo prático de goroutines e channels:

package main

import (
    "fmt"
    "time"
)

func processarDados(id int, canal chan string) {
    time.Sleep(1 * time.Second)
    // Envia a mensagem para o channel
    canal <- fmt.Sprintf("Dados do processo %d processados!", id)
}

func main() {
    // Cria um channel de strings
    canal := make(chan string)

    // Inicia 3 goroutines, cada uma enviando uma mensagem para o channel
    for i := 1; i <= 3; i++ {
        go processarDados(i, canal)
    }

    // Recebe 3 mensagens do channel e as imprime
    for i := 1; i <= 3; i++ {
        msg := <-canal
        fmt.Println(msg)
    }
}

 

Neste código, três goroutines são iniciadas para processar dados. Cada uma, ao terminar, envia uma mensagem para o canal. A função main espera, por meio do loop for, até receber as três mensagens, garantindo que a execução principal só termine após todas as goroutines de processamento terem concluído suas tarefas e enviado o resultado.
 

Programação Assíncrona: O Papel de sync e sync/atomic

Embora goroutines e channels sejam a forma primária e mais idiomática de lidar com concorrência em Go, a linguagem também oferece pacotes para cenários mais específicos, como o sync e o sync/atomic.

sync.WaitGroup: Usado para esperar que um grupo de goroutines termine suas execuções. É uma alternativa comum aos channels quando não há necessidade de trocar dados, apenas de sincronizar o fim de tarefas.

sync.Mutex: Oferece um mutex (mutual exclusion lock) para proteger o acesso a dados compartilhados, uma técnica mais tradicional de concorrência. Embora não seja a abordagem preferida, é útil em cenários onde a comunicação via canais seria excessivamente complexa.

sync/atomic: Oferece operações atômicas, ou seja, operações de baixo nível que não podem ser interrompidas, garantindo a integridade de dados compartilhados, como contadores, sem a necessidade de um mutex completo.
 

Conclusão: Simplicidade e Eficiência

A abordagem do Go para concorrência é revolucionária por sua simplicidade e poder. Ao fornecer goroutines (trabalhadores) e channels (canais de comunicação) como elementos fundamentais da linguagem, ele permite que desenvolvedores criem programas concorrentes de forma intuitiva, segura e escalável, sem a complexidade e os riscos de erros frequentemente associados a outras linguagens. Essa é uma das razões pelas quais o Go é tão valorizado em ambientes de alta performance e sistemas de rede, onde a concorrência é um requisito essencial.

Compartilhe este post:
Voltar para a Home