Aula 27 – Tutorial Golang – Sincronização de Canais
Aula 27 – Tutorial Golang – Sincronização De Canais
Página principal do blog
Todas as aulas desse curso
Aula 26 Aula 28
Redes Sociais:
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
Aula 27 – Tutorial Golang – Sincronização de canais
Fontes usadas para esse post:
- Full Cycle Channel – Go Lang: Go routines e channels
- https://golangr.com/channel-synchronization/
- https://gobyexample.com/channel-synchronization
Canais tem uma forma um pouco diferente de receber e enviar dados.
ch <- values // enviando em um canal
value := <- ch // Recebendo em um canal e atribuindo o valor
<-ch // Recebendo em um canal e descartando o resultado
close(ch) // Fechando um canal. Isso significa que o canal não pode enviar nenhum dado
Os channels funcionam parecido com as threads tradicionais, mas, ao invés das threads serem geradas pelo schedule do sistema operacional, elas são geradas pela runtime do Go.
O runtime são os códigos das bibliotecas Go.
Então, quando a gente compila um código Go, o runtime dele é compilado junto com o código, juntando os códigos do runtime com o código do nosso programa.
Parecido com o que ocorre com a JVM do java e o próprio código de um programa em java, que roda dentro do ambiente virtual JVM.
O pessoal que criou o golang, para simplificar o uso de fluxos de rotinas concorrentes, ou seja, as threads, resolveu criar o próprio gerenciador de tarefas, isto é, o schedule, que criam as próprias threads.
Por isso quem gerencia as threads em Go é a própria runtime.
Essas threads são chamadas de threads de nível de usuário, ou terra de usuário, ou threads verdes ou ainda threads leves.
Cada thread go ocupa 2kb de memória, cada thread tradicional ocupa 1 mega.
Schedule
O Schedule das threads tradicionais, ou seja, o cara responsável pelo cronograma de tarefas dentro do SO, pode usar o escalonamento preemptivo ou cooperativo.
O preemptivo é o que define um tempo de processamento para cada tarefa, não importando se a tarefa foi ou não concluída, o processamento é interrompido em função de outra tarefa.
O cooperativo, a tarefa é processada o máximo possível, só liberando o processador caso termine de executar, as tarefas gerenciam seu próprio ciclo de vida.
Depois de ser atribuído a um worker, cabe à tarefa se ela deve ou não ser liberada de um trabalhador, ou seja, devolver o controle ao Schedule.
O trabalho do agendador é apenas atribuir tarefas a qualquer worker que esteja livre.
Go usa por padrão o escalonamento cooperativo, mas também é possível usar o escalonamento preemptivo.
Esse escalonamento pode levar um processo a monopolizar os recursos de processamento.
Para evitar isso, o Go estabelece algumas regras, por exemplo, se tiver que acessar disco, ou fazer qualquer outra ação não bloqueante, vai para a próxima goroutine que precisa ser processada.
Ou se, por exemplo, tiver que fazer uma chamada externa a uma API, também faz o processamento de outra thread (goroutine), se tiver que dá um timeout ou um sleep, mesma coisa.
Channel synchronization
Os canais podem ser usados para sincronizar goroutines.
Um canal pode fazer uma goroutine esperar até terminar.
O canal pode então ser usado para notificar uma 2ª goroutine.
Imagine que você tenha várias goroutines.
Às vezes, uma goroutine precisa ser concluída antes que você possa iniciar a próxima (síncrona).
Isso pode ser resolvido com canais.
Podemos usar canais para sincronizar a execução entre essas goroutines.
Aqui está um exemplo de uso de um recebimento de bloqueio para esperar que uma goroutine termine.
Para aguardar a conclusão de várias goroutines, você pode usar um WaitGroup.
Esta é a função que vamos executar em uma goroutine.
O canal done será notificado que o trabalho da função foi terminado.
func task(done chan bool) {
fmt.Print("working...")
time.Sleep(time.Second)
fmt.Println("done")
done <- true
}
Na main(), é inicializada uma goroutine chamada task, dando a ela o canal done que recebe a notificação.
O código <-done, bloqueia até receber uma notificação do task no canal done.
func main() {
done := make(chan bool, 1)
go task(done)
<-done
}
Se você remover a linha <-done do código, o programa seria encerrado antes mesmo da task iniciar.
Código completo
package main
import (
"fmt"
"time"
)
func task(done chan bool) {
fmt.Print("working...")
time.Sleep(time.Second)
fmt.Println("done")
done <- true
}
func main() {
done := make(chan bool, 1)
go task(done)
<-done
}
Execute o programa com:
go run channel_synchronization.go
Saída:
working…done
Código 02 completo
package main
import (
"fmt"
"time"
)
func task(taskId int, msg chan int) {
for res := range msg {
fmt.Println("Task: ", taskId, "Message: ", res)
time.Sleep(time.Second)
}
}
func main() {
msg := make(chan int)
go task(1, msg)
//go task(2, msg)
//go task(3, msg)
//go task(4, msg)
for i := 0; i < 10; i++ {
msg <- i
}
}
Saída:
Task: 1 Message: 0
Task: 1 Message: 1
Task: 1 Message: 2
Task: 1 Message: 3
Task: 1 Message: 4
Task: 1 Message: 5
Task: 1 Message: 6
Task: 1 Message: 7
Task: 1 Message: 8
Task: 1 Message: 9
Sincronizando goroutines
Dá pra fazer uma goroutine esperar por outra.
Você pode fazer isso com uma instrução if e lendo o valor do canal.
No código abaixo, a goroutine task2 aguardará a finalização da goroutine task.
Observação
As goroutines (task, task2) podem ser síncronas o tempo todo enquanto são executadas simultaneamente.
Como tudo é concorrente, você ainda pode usar o thread main do seu programa ao mesmo tempo.
package main
import "fmt"
import "time"
func task(done chan bool) {
fmt.Print("Task 1 (goroutine) running...")
time.Sleep(time.Second)
fmt.Println("done")
done <- true
}
func task2() {
fmt.Println("Task 2 (goroutine)")
}
func main() {
done := make(chan bool, 1)
go task(done)
fmt.Println("Im in the main thread!")
if <- done {
go task2()
fmt.Scanln()
}
}
Executando o programa com:
go run channel_synchronization.go
Você verá a saída abaixo e o programa vai ficar esperando que você tecle qualquer coisa para encerrar, por causa do fmt.scanln().
Saída:
Im in the main thread!
Task 1 (goroutine) running…done
Task 2 (goroutine)