- read

Concorrência em Go

Lucas Maia 22

Channels

Channels promovem um meio de comunicação entre duas goroutines e pode ser usado para sincronizar a execução entre elas.

Representamos os channels com a palavra chan seguido do seu tipo. Vamos ver um exemplo usando channels e goroutines.

func producer(channel chan string) {
channel <- "Hello, World!"
}


func receiver(channel chan string) {
msg := <- channel
fmt.Println(msg)
}
func main() {
channel := make(chan string)
defer close(channel)

go producer(channel)
go receiver(channel)

time.Sleep(time.Second)
}

Nesse exemplo, temos duas goroutines explícitas representadas pelas funções producer e receiver. O resultado final é o mesmo apresentado no código anterior, mas agora fizemos as duas goroutines trocarem uma mensagem entre si.

Obs: Note que não passamos a string que queremos imprimir na tela para o método, passamos somente o channel.

Utilizando channels como utilizamos nesse exemplo, conseguimos sincronizar as duas goroutines, pois quando o producer tentar mandar uma mensagem no canal, ele irá esperar até que o receiver esteja pronto para consumir a mensagem.

Channel Direction:

É possível especificar a direção do channel, restringindo quem vai escrever ou ler no channel. Isso pode ser um ponto positivo pois somente olhando a assinatura do método que está recebendo o channel é possível saber o que vai acontecer, além de se ter um controle em que parte do seu código pode-se ler ou escrever no channel.

Para determinar se só será possível escrever no canal, é assim que devemos escrever:

func producer(channel chan <- string) {
channel <- "Hello, World!"
channel <- "Hello"
}

Por outro lado, se você quiser escrever uma função que somente leia do seu channel, devemos escrever dessa maneira:

func receiver(channel <- chan string) {
for c := range channel {
fmt.Println(c)
}
}

Se tentarmos ler de um canal com direção para somente escrita ou escrever em um channel com direção para somente leitura, nas duas situações um erro do compilador será mostrado.

Um channel pode não ter direção, aceitando escrita e leitura. Esse tipo de channel é conhecido como bidirecional. Podemos passar esse tipo para funções que recebem canais que tenham direção, mas não podemos passar canais que tenham direção para funções que recebam um canal bidirecional.

Select:

Em Go temos um statement chamado select que funciona como se fosse um switch, mas exclusivo para channels. O select funciona da seguinte maneira: Ele pega o primeiro canal que está pronto e envia ou recebe uma mensagem para ele, dependendo do que escrevemos no código. Caso mais de um channel esteja disponível o select escolhe um randomicamente. Se não tiver nenhum canal disponível, o select fica bloqueado até um canal estar pronto. Vamos ver um exemplo usando select para consumir as mensagens produzidas por dois canais diferentes:

func producer(channel chan<- string, message string) {
for {
channel <- message
time.Sleep(time.Second)
}
}

func receiver(channel1 <-chan string, channel2 <-chan string) {
for {
select {
case message1 := <-channel1:
fmt.Println(message1)
case message2 := <-channel2:
fmt.Println(message2)
}
}
}

func main() {
channel1 := make(chan string)
channel2 := make(chan string)

defer close(channel1)
defer close(channel2)

go producer(channel1, "message from channel 1")
go producer(channel2, "message from channel 2")

go receiver(channel1, channel2)

var input string
_, err := fmt.Scanln(&input)
if err != nil {
log.Fatalln(err)
}
}

WaitGroups:

Waitgroups é utilizado para esperar todas as goroutines que foram lançadas anteriormente. Se um waitgroup for passado explicitamente entre as funções, devemos utilizar um ponteiro para isso. Isso será importante se, por exemplo, nosso worker tenha que lançar novas goroutines.

Isso é tudo por hoje. Se você gostou e esse conteúdo te ajudou, deixe umas palmas e me siga :)