Go: Comunicação entre Processos

De Aulas
Revisão de 20h17min de 6 de setembro de 2022 por Admin (discussão | contribs) (Criou página com 'Afluentes: Sistemas Distribuídos e Mobile = Comunicação entre Processos = A comunicação entre processos no go é feito por '''canais'''. Eles são comparados aos [h...')
(dif) ← Edição anterior | Revisão atual (dif) | Versão posterior → (dif)

Afluentes: Sistemas Distribuídos e Mobile

Comunicação entre Processos

A comunicação entre processos no go é feito por canais. Eles são comparados aos pipes na linguagem C. Cada canal tem um tipo associado. chan T é um canal do tipo T.

// Canal a do tipo int
a := make(chan int)

Para usar o canal, usamos uma seta direcional

// estamos lendo do canal a e jogando para a variável data
data := <- a

// estamos escrevendo as informações de data para o canal a
a <- data

Envios e recebimentos de informações pelos canais são bloqueantes, ou seja. Quando você envia algo, a função aguarda até que outra gorotine receba o dado. Da mesma forma funciona o envio. Isso também é últim para sincronização de gorotines.

Exemplo de comunicação usando canais

 1package main
 2
 3import (
 4	"fmt"
 5	"time"
 6)
 7
 8func child(fc chan string, cc chan string) {
 9	msg := <-cc                                   // aguarda a mensagem do pai
10	fmt.Println("CHILD: Father says " + msg)      // imprime na tela
11	time.Sleep(2 * time.Second)                   // aguarda 2 segundos
12	fmt.Println("CHILD: I will response just ok") // imprime na tela
13	fc <- "ok!"                                   // envia a resposta pelo canal do pai
14}
15
16func main() {
17	fatherChannel := make(chan string)                      // canal de comunicação do pai
18	childCHannel := make(chan string)                       // canal de comunicação do filho
19	go child(fatherChannel, childCHannel)                   // chama a rotina child paralelamente
20	fmt.Println("FATHER: I will say hello to my child")     // imprime na tela
21	msg := "hello"                                          // cria a variável msg e atribui a string hello
22	childCHannel <- msg                                     // envia a mensagem hello pelo canal do filho
23	msg = <-fatherChannel                                   // aguarda a resposta do filho
24	fmt.Println("FATHER: I receive " + msg + " from child") // imprime na tela
25}

Canais Unidirecionais

Os canais unidirecionais são apenas para envio ou recebimento. Pode-se converter um bidirecional para unidirecional, mas não o contrário.

sendChannel := make(chan<- string) // canal apenas de envio
func send(ch chan<- string) // diz que o canal é só de envio

Fechando Canais

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7func child(c chan int) {
 8	defer close(c) // Fecha o canal quando terminar a função
 9	for i := 0; i < 3; i++ {
10		c <- i // Envia informações algumas vezes pelo canal
11	}
12}
13func main() {
14	ch := make(chan int)
15	go child(ch)
16	for {
17		res, ok := <-ch  // res recebe a resposta e ok se o canal está aberto
18		if ok == false { // Se ok é falso, então o canal foi fechado
19			fmt.Println("Canal fechado", ok)
20			break
21		}
22		fmt.Println("Recebi", res, ok) // Vai imprimindo o que recebe
23	}
24}

Canais com buffer

Os canais com buffer são bloqueados caso a capacidade tenha sido excedida. Por exemplo. Se o seu buffer tem capacidade de 5 inteiros, e você quer enviar 10. Se seu programa não conseguiu fazer qualquer leitura, o próximo envio ficará bloqueado até que libere um espaço.

 1package main
 2
 3import (
 4	"fmt"
 5	"time"
 6)
 7
 8func child(ch chan int) {
 9	defer close(ch)
10	// Já vamos tentar enviar 5, mas somente pode ter 2 inteiros no buffer
11	for i := 0; i < 5; i++ {
12		ch <- i // Caso o buffer esteja cheio, essa linha fica bloqueada
13		fmt.Println("enviei", i, "com sucesso para o canal")
14	}
15}
16func main() {
17	// Canal com capacidade de 2 inteiros.
18	ch := make(chan int, 2) //Veja que temos um parâmetro extra
19	go child(ch)
20	time.Sleep(2 * time.Second)
21	for v := range ch {
22		fmt.Println("recebi o valor", v, "do canal")
23		time.Sleep(2 * time.Second)
24
25	}
26}

Usando select nos canais

podemos usar select (sintaxe tipo o switch-case) para múltiplas operações de enviar e receber dados via canais. O select bloqueia até alguma das operações recebeu algo ou enviou (de forma síncrona). Se mais de uma rotina de mensagem terminou, escolhe entre uma aleatoriamente.

Veja que no nosso exemplo vai sempre imprimir apenas o resultado do child2. Isso porque o child1 leva mais tempo para execução e o select pega o primeiro. Daí o programa fecha.

Ok, mas pra que eu usaria isso?

Existem várias situações em que precisamos atividades competitivas, por exemplo testar qual caminho de roteadores até um determinado site está mais rápido. O que retornar primeiro é o escolhido. Ou mesmo imagine que uma tabela de banco de dados está replicada em dois ou mais servidores. Pode-se fazer uma requisição de teste para cada servidor, o que retornar antes será a tabela a ser acessada.

 1package main
 2
 3import (
 4	"fmt"
 5	"time"
 6)
 7
 8func child1(ch chan string) {
 9	time.Sleep(6 * time.Second)
10	ch <- "do child1"
11}
12func child2(ch chan string) {
13	time.Sleep(3 * time.Second)
14	ch <- "do child2"
15
16}
17func main() {
18	ch1 := make(chan string)
19	ch2 := make(chan string)
20	go child1(ch1)
21	go child2(ch2)
22	// Podemos usar select
23	select {
24	case s1 := <-ch1:
25		fmt.Println(s1)
26	case s2 := <-ch2:
27		fmt.Println(s2)
28	}
29}

Deadlocks ¯\_(ツ)_/¯

Quando usamos canais, é importante ficarmos atentos para não gerar deadlocks. Se uma gorotine enviar dados para um canal esperando que outra gorotine a receba, mas essa outra rotina não recebe, o programa entrará em estado de deadlock. Veja o exemplo a baixo:

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7func child(c chan bool) {
 8	a := <-c
 9	fmt.Printf("Recebi %t e estou enviando o contrário\n", a)
10	c <- !a
11}
12func main() {
13	ch := make(chan bool)
14	go child(ch)
15	response := <-ch
16	fmt.Printf("Acho que nunca vou receber porque não enviei nada %t\n", response)
17	fmt.Println("main function")
18}

se executarmos o programa, ele vai retornar algo como:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
        /home/saulo/dev/golang/src/aulas.com/distribuidos/deadlock/deadlock.go:15 +0x85

goroutine 6 [chan receive]:
main.hello(0x0)
        /home/saulo/dev/golang/src/aulas.com/distribuidos/deadlock/deadlock.go:8 +0x2c
created by main.main
        /home/saulo/dev/golang/src/aulas.com/distribuidos/deadlock/deadlock.go:14 +0x6f
exit status 2

Exclusão Múltua - Mutex

Como mecanismo de bloqueio, as gorotines utilizam o Mutex. Ele tenta garantir que apenas uma gorotine esteja executando a região crítica de um código em um dado momento, evitando que outras gorotines também tentem. Isso evita que a race condition ocorra.

Uma gorotine pode bloquear e desbloquear uma região crítica. essa região ficará bloqueada até que ela seja desbloqueada.

Exemplo
 1package main
 2
 3import (
 4	"fmt"
 5	"sync"
 6)
 7
 8// x é uma região crítica
 9var x = 0
10
11func increment(wg *sync.WaitGroup, m *sync.Mutex) {
12	// Estamos entrando em uma região crítica, então bloqueamos
13	m.Lock()
14	x = x + 1
15	// Ao sair da região crítica desbloqueamos
16	m.Unlock()
17	wg.Done()
18}
19func main() {
20	var w sync.WaitGroup
21	var m sync.Mutex
22	for i := 0; i < 1000; i++ {
23		w.Add(1)
24		go increment(&w, &m)
25	}
26	w.Wait()
27	fmt.Println("Valor final de x", x)
28}

Referências