Goroutines semaphores e pipeline pattern
Tags: beginners, todayilearned, go
Published: 2022-06-04 02:57:59

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.

```go
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)
	}
}
```

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

```go
func generate(numbers ...int) chan int {
	channel := make(chan int)

	go func() {
		for _, number := range numbers {
			channel <- number
		}
	}()

	return channel
}

```

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

```go
func divide(input chan int) chan int {
	channel := make(chan int)

	go func() {
		for number := range input {
			channel <- number / 2
		}
		close(channel)
	}()

	return channel
}
```

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

```go
func main() {
	numbers := generate(2, 4, 6)
	result := divide(numbers)

	fmt.Println(<-result)
	fmt.Println(<-result)
	fmt.Println(<-result)
}
```

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


```go
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
}
```
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 

```bash
❯ 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
```

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:

```go
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
}
```