Aula 29 - Tutorial Golang - Select
Fontes usadas para esse post:
Select
A instrução
select permite que uma
goroutine aguarde várias operações de comunicação.
Um
select bloqueia até que um de seus
cases possa ser executado, então ele executa esse
case.
Se vários
cases estiverem prontos para serem executados, um deles é escolhido aleatoriamente.
A sintaxe é semelhante a
switch, o que muda é que cada uma das instruções
case é uma operação de canal.
package main
import (
"fmt"
"time"
)
func server1(ch chan string) {
// sleeps for 6 seconds then writes the
// text "from server1" to the channel ch
time.Sleep(6 * time.Second)
ch <- "from server1"
}
func server2(ch chan string) {
// sleeps for 3 seconds then writes the
// text "from server2" to the channel ch
time.Sleep(3 * time.Second)
ch <- "from server2"
}
func main() {
output1 := make(chan string)
output2 := make(chan string)
// calls the go Goroutines server1
go server1(output1)
// calls the go Goroutines server2
go server2(output2)
// select statement blocks until one
// of its cases is ready
select {
case s1 := <-output1:
fmt.Println(s1)
case s2 := <-output2:
fmt.Println(s2)
}
}
Saída:
from server2
Uso prático do select
A razão para ter chamado as funções de
server1() e
server2(), é para ilustrar o uso prático do
select .
Vamos supor que temos um aplicativo de missão crítica e precisamos retornar a saída para o usuário o mais rápido possível.
O banco de dados do aplicativo é replicado e armazenado em diferentes servidores na nuvem, mundo afora.
Suponha que as funções
server1() e
server2(), estejam de fato se comunicando com 2 desses servidores.
O tempo de resposta de cada servidor depende da carga de cada um e do atraso da rede.
Enviamos a solicitação para ambos os servidores e aguardamos nos canais correspondentes a resposta usando a instrução
select.
O servidor que responder primeiro é escolhido pelo
select e a outra resposta é ignorada.
Desta forma, podemos enviar a mesma solicitação para vários servidores e retornar a resposta mais rápida ao usuário :).
Default case - Caso Padrão
O caso padrão em uma instrução
select, é executado quando nenhum dos outros casos está pronto.
Isso geralmente é usado para impedir que a instrução
select bloqueie.
Vamos a um exemplo.
package main
import (
"fmt"
"time"
)
func process(ch chan string) {
// sleeps for 10.5 seconds then writes the
// text "process successful" to the channel ch
time.Sleep(10500 * time.Millisecond)
ch <- "process successful"
}
func main() {
ch := make(chan string)
go process(ch)
//infinite loop
for {
// sleeps for 1000 milliseconds (1 second)
// during the start of each iteration
time.Sleep(1000 * time.Millisecond)
// and then performs a select operation
select {
case v := <-ch:
// will be executed after 10.5 seconds when func
// process will write in channel "process successful"
fmt.Println("received value: ", v)
return
default:
fmt.Println("no value received")
}
}
}
Saída:
no value received
no value received
no value received
no value received
no value received
no value received
no value received
no value received
no value received
no value received
received value: process successful
No programa acima, a função
process() dorme por
10.500 milissegundos (
10,5 segundos).
Depois escreve "
process successful" no canal
ch.
Esta função é chamada na
main() em uma
goroutine.
Em seguida temos um loop
for infinito, iniciado na
goroutine principal.
O
loop infinito dorme por
1000 milissegundos (
1 segundo) durante o início de cada iteração e em seguida, executa uma operação de seleção.
Durante os primeiros
10.500 milissegundos, o primeiro caso da instrução
select case v := <-ch:, não estará pronto, pois a
goroutine process escreverá no canal
ch só após
10.5 segundos.
Portanto, o
default será executado durante esse tempo e o programa imprimirá
no value received 10 vezes.
Após
10,5 segundos, a
goroutine process escreve
process successful no
ch.
Agora, o primeiro caso da instrução
select será executado e o programa imprimirá
received value: process successful, e em seguida, encerra.
Deadlock e Default Case
No programa abaixo, criamos um canal
ch.
Tentamos ler deste canal dentro do
select.
A instrução
select será bloqueada para sempre, pois nenhuma outra
goroutine está escrevendo nesse canal e portanto, resultará em um
impasse (
deadlock).
package main
func main() {
ch := make(chan string)
select {
case <-ch:
}
}
Este programa entrará em pânico no tempo de execução com a seguinte mensagem.
Saída:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
C:/Users/user01/projects/go/learning_golang/28 - select/select.go:6 +0x2c
exit status 2
Se um
default case estivesse presente, esse
deadlock não aconteceria, pois o
default case será executado quando nenhum outro
case estiver pronto.
Veja logo abaixo, o programa acima reescrito com um
default case.
package main
import "fmt"
func main() {
ch := make(chan string)
select {
case <-ch:
default:
fmt.Println("default case executed")
}
}
Saída:
default case executed
Seleção Aleatória
Quando vários
cases em uma instrução
select estiverem prontos, um deles será executado aleatoriamente.
Se você executar este programa várias vezes, a saída variará entre "
from server1" ou "
from server2", dependendo de qual case for escolhido aleatoriamente.
package main
import (
"fmt"
"time"
)
func server1(ch chan string) {
ch <- "from server1"
}
func server2(ch chan string) {
ch <- "from server2"
}
func main() {
output1 := make(chan string)
output2 := make(chan string)
go server1(output1)
go server2(output2)
time.Sleep(1 * time.Second)
select {
case s1 := <-output1:
fmt.Println(s1)
case s2 := <-output2:
fmt.Println(s2)
}
}
Gotcha - Select Vazio
package main
func main() {
select {}
}
A instrução
select é bloqueada até que um de seus cases seja executado.
Nesse caso, a instrução
select não possui nenhum case e portanto, será bloqueada para sempre, resultando em um
impasse (
deadlock).
Este programa entrará em pânico com a seguinte saída.
Saída:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select (no cases)]:
main.main()
C:/Users/user01/projects/go/learning_golang/28 - select/select.go:4 +0x17
Eu fico por aqui e agradeço a você pela audiência.
Até mais. :)
Meus links de afiliados:
Obrigado e bons estudos. ;)