DEV Community

Yan.ts
Yan.ts

Posted on

Goroutines semaphores e pipeline pattern

O semáforo no Go é como se uma flag, que vai ser um channel que vai se comunicar com as routines, quando o semáforo apontar que deu tudo certo a ideia é q parem os canais.

func main() { channel := make(chan int) ok := make(chan bool) go func() { for i := 0; i < 10; i++ { channel <- i } ok <- true }() go func() { for i := 0; i < 10; i++ { channel <- i } ok <- true }() go func() { <-ok <-ok close(channel) }() for number := range channel { fmt.Println(number) } } 
Enter fullscreen mode Exit fullscreen mode

Nesse exemplo a variável ok serve como o semáforo, perceba que na terceira função eu espero que a variável ok tenha sido esvaziada duas vezes para só então poder fechar o channel

Pipeline pattern

Até agora todas as vezes que usei o channel tinham sido de forma isolada, com a pipeline fazemos funções que retornam um channel e outras funções que recebem esse channel retornado e continuam o processamento. Aprendi a trabalhar com pipelines através da seguinte abordagem.

primeiro criei uma função generate que recebe numeros inteiros e retorna um channel de inteiros

func generate(numbers ...int) chan int { channel := make(chan int) go func() { for _, number := range numbers { channel <- number } }() return channel } 
Enter fullscreen mode Exit fullscreen mode

E depois uma segunda função para dividir que recebe um channel de inteiros e retorna outro channel de inteiros

func divide(input chan int) chan int { channel := make(chan int) go func() { for number := range input { channel <- number / 2 } close(channel) }() return channel } 
Enter fullscreen mode Exit fullscreen mode

porem no caso dessa função enquanto eu vou atribuindo o valor para o channel eu já atribuo ele dividido por 2.

Minha função main ficou da seguinte forma

func main() { numbers := generate(2, 4, 6) result := divide(numbers) fmt.Println(<-result) fmt.Println(<-result) fmt.Println(<-result) } 
Enter fullscreen mode Exit fullscreen mode

Onde em ordem os prints exibem 1 2 3. Mas o que tá acontecendo de fato é. A função generate recebe o numero 2, atribui ele ao seu channel, e tenta atribuir o valor 4 em seguida, não consegue pois o channel ainda não foi esvaziado, nesse momento a função divide inicia e recebe o valor do channel da primeira função e atribui ao seu channel, ele também vai tentar atribuir o próximo numero e não vai conseguir, então ele chama o println para poder esvaziar o seu channel e isso roda 3 vezes até os 3 números terem sido processados

Fun in

Imagine que temos 3 processos rodando em paralelo cada um com o seu próprio canal, agora temos uma segunda função que recebe os canais dos 3 processos e retorna um novo canal mandando os resultados dos 3 processos

O fun in é um afunilamento que faz com que não precisemos olhar para os 3 channels e sim para apenas 1 e termos o resultado dos 3

func main() { x := funnel(generateMsg("Hello"), generateMsg("World")) for i := 0; i < 10; i++ { fmt.Println(<-x) } } func generateMsg(s string) <-chan string { channel := make(chan string) go func() { for i := 0; ; i++ { channel <- fmt.Sprintf("String %s - Value: %d", s, i) time.Sleep(time.Duration(rand.Intn(255)) * time.Millisecond) } }() return channel } func funnel(channel1, channel2 <-chan string) <-chan string { channel := make(chan string) go func() { for { channel <- <-channel1 } }() go func() { for { channel <- <-channel2 } }() return channel } 
Enter fullscreen mode Exit fullscreen mode

ps: o operador <- <- me deixou um pouco confuso no inicio porem ele significa que estamos pegando o valor do channel1 e passando para a variavel channel

o output dessa função vai ser

❯ go run main.go String World - Value: 0 String Hello - Value: 0 String World - Value: 1 String Hello - Value: 1 String World - Value: 2 String World - Value: 3 String Hello - Value: 2 String Hello - Value: 3 String World - Value: 4 String World - Value: 5 
Enter fullscreen mode Exit fullscreen mode

então somente olhando para a variável x dá para ter o resultado dos dois channels que estão rodando em paralelo

Fan out

Basicamente o Fan out é o processo oposto do fun in, onde vamos ter apenas 1 canal mas queremos distribuir ele pro diversos canais

para fazer o fan out podemos reutilizar o código do algoritmo de divide basta que pegarmos o valor inicial e passarmos para a função divide mais de uma vez, por exemplo:

func main() { c := generate(5, 10) d1 := divide(c) d2 := divide(c) fmt.Println(<-d1) fmt.Println(<-d2) } func generate(numbers ...int) chan int { channel := make(chan int) go func() { for _, n := range numbers { channel <- n } close(channel) }() return channel } func divide(input chan int) chan int { channel := make(chan int) go func() { for number := range input { channel <- number / 2 } close(channel) }() return channel } 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)