Aula 29 – Tutorial Golang – Select

Aula 29 – Tutorial Golang – Select

Tutorial Golang - Channel Synchronization

Tutorial Golang – Channel Synchronization

Pacote Programador Fullstack

Pacote Programador Fullstack

Página principal do blog

Todas as aulas desse curso

Aula 28                        Aula 30

Redes Sociais:

facebook

Meus links de afiliados:

Hostinger

Digital Ocean

One.com

Melhore seu NETWORKING

https://digitalinnovation.one/

Participe de comunidades de desenvolvedores:

Fiquem a vontade para me adicionar ao linkedin.

E também para me seguir no https://github.com/toticavalcanti.

Código final da aula:

https://github.com/toticavalcanti

Canais do Youtube:

Toti

Backing Track / Play-Along

Código Fluente

Putz!

Vocal Techniques and Exercises

PIX para doações

PIX Nubank

PIX Nubank

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. 🙂

Página principal do blog

Todas as aulas desse curso

Aula 28                        Aula 30

Meus links de afiliados:

Hostinger

Digital Ocean

One.com

Obrigado e bons estudos. 😉

 

 

About The Author
-

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>