DEV Community

Igor Melo
Igor Melo

Posted on

Golang: Desmistificando channels - Select

Select: selecionando o canal “mais rápido”

O desafio

Imagina que você quer consultar as informações de um local a partir de um CEP.

Existem algumas APIs de CEP, mas você quer que o resultado seja o mais rápido possível. O que você faz?

Você poderia fazer um benchmark para ver qual é a API mais rápida, mas isso não seria muito preciso, porque uma certa API pode ficar mais lenta em certos horários, ou pode ser mais rápida para alguns CEPs e não para outros...

Uma estratégia que podemos usar é disparar uma requisição para cada uma e usar a primeira que responder com um status de 200 OK, e essa é justamente a ideia do projeto CEP Promise do BrasilAPI.

CEP Promise

Dessa forma não só ganhamos em performance como também em disponibilidade, porque mesmo se uma API falhar, ainda vamos ter outra(s) de fallback.

Para implementar algo parecido, podemos usar o select.

O select é como se fosse um switch, onde cada case é uma tentativa de comunicar com um canal.

O primeiro case com um recebimento ou envio pronto para ser executado vai ser o case que a goroutine vai entrar.

Exemplo:

select {
case <-time.After(time.Second):
    fmt.Println("1 segundo")

case <-time.After(500 * time.Millisecond):
    fmt.Println("500ms")
}

// "500ms"
Enter fullscreen mode Exit fullscreen mode

O select por padrão pausa a execução da goroutine. Um select sem case vai parar a goroutine indefinidamente:

package main

func main() {
    ...

    // Se não tiver outra goroutine, vai causar um deadlock e encerrar
    select {}
}
Enter fullscreen mode Exit fullscreen mode

O select, assim como o switch, também tem um caso default.

Ele cai no default quando nenhum dos outros casos está pronto para ser executado.

read := false

// combinando um loop com um select
for !read {

    select {
    case x := <-ch:
        fmt.Println("li do channel:", x)
        read = true

    default:
        fmt.Println("channel ainda não está preparado para leitura")
        time.Sleep(100 * time.Millisecond)
    }
}

// "channel ainda não está preparado para leitura"
// "channel ainda não está preparado para leitura"
// "li do channel: 123"
Enter fullscreen mode Exit fullscreen mode

No exemplo de consulta de CEP, um código fictício para pegar o primeiro CEP que retornar seria:

cep := "12345678"
var info CepInfo

select {
case info = <-cepInfoCorreios(cep):
    fmt.Println("Correios retornou primeiro")

case info = <-cepInfoViaCep(cep):
    fmt.Println("ViaCEP retornou primeiro")
}

fmt.Println(info.City)

// obs.: num caso real você teria que cancelar as requisições HTTP também
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
giz_l_ profile image
Giselle Lisboa

Gostei, foi bem útil para mim. Eu estava estudando esse tema.