Um Tutorial para a Linguagem de Programação Go – parte 8
Agora chegamos aos processos de comunicação e programação concorrente. É um assunto vasto, então para ser breve, assumimos alguma familiaridade com o tópico.
Um programa clássico neste estilo é a peneira de números primos. (A peneira de Eratosthenes (The sieve of Eratosthenes) é mais eficiente do que o algoritmo aqui apresentado, mas estamos mais interessados em concorrência do que em algoritmos no momento). Ela (a peneira) funciona pegando uma cadeia de todos os números naturais e introduzindo uma sequência de filtros, uma para cada número primo, para destacar os múltiplos daquele número primo. A cada passo temos uma sequência de filtros dos primos até então, e o próximo número a ser retirado é o próximo primo, o qual dispara a criação do próximo filtro na cadeia.
Aqui está um diagrama de fluxo; cada caixa representa um elemento filtro cuja criação é disparada pelo primeiro número que fluiu dos elementos anteriores.
Para criar uma cadeia de inteiros, usamos um channel Go, o qual, emprestado dos descendentes de CSP, representa um canal de comunicação que pode conectar duas computações concorrentes. Em Go, variáveis channel são referências a um objeto de tempo de execução que coordena a comunicação; como feito com maps e slices, use make para criar um novo channel.
Aqui está a primeira função em progs/sieve.go:
// Envia a sequência 2, 3, 4, ... para o channel ch
func generate(ch chan int) {
for i := 2; ; i++ {
ch <- i // Envia 'i' para o channel 'ch'
}
}
A função generate envia a sequência 2, 3, 4, 5, … para seu argumento channer, ch, usando o operador binário de comunicações <-. Operações com channel, bloqueiam, então se não existir um receptor para o valor em ch, a operação esperará até que um (receptor) se torne disponível.
A função filtro tem três argumentos: um channel de entrada, um channel de saída e um número primo. Ela copia valores da entrada para a saída, descartando qualquer coisa divisível pelo número primo. O operador unário de comunicação <- (recebe) recupera o próximo valor no channel.
// Copia os valores do channel in para o channel out,
// removendo aqueles divisíveis por prime
func filter(in, out chan int, prime int) {
for {
i := <-in; // Receive value of new variable 'i' from 'in'.
if i % prime != 0 {
out <- i // Send 'i' to channel 'out'.
}
}
}
O gerador (método generate) e os filtros (método filter) executam concomitantemente. Go tem seu próprio modelo de processos/coroutines, para evitar confusões de notação chamamos processamento concomitante em Go de goroutines. Para “startar” uma goroutine, chame a função precedendo a chamada com a palavra-chave go; Isto inicia a função executando em paralelo com o processo corrente mas no mesmo espaço de endereçamento:
go sum(hugeArray); // calcula a soma em background
Se você quer saber quando o cálculo está pronto, passe um channel o qual pode ser verificado:
ch := make(chan int);
go sum(hugeArray, ch);
// ... faça alguma coisa enquanto isso
result := <-ch; // espera por e recupera o resultado
Voltemos à nossa peneira de números primos. Aqui está a forma como a canalização da peneira é costurada:
func main() {
ch := make(chan int); // Cria um novo channel.
go generate(ch); // Starta generate() como uma goroutine.
for {
prime := <-ch;
fmt.Println(prime);
ch1 := make(chan int);
go filter(ch, ch1, prime);
ch = ch1
}
}
A linha 29 cria o channel inicial e passa ao método generate, o qual então inicia. Como cada número primo sai do channel, um novo filtro é adicionado ao canal e sua saída torna-se um novo valor de ch.
O programa da peneira pode ser forçado a usar um padrão comum em seu estilo de programação. Aqui está uma variante do método generate, retirado de progs/sieve.go:
func generate() chan int {
ch := make(chan int);
go func(){
for i := 2; ; i++ {
ch <- i
}
}();
return ch;
}
Esta versão faz toda a configuração inicial internamente. Ela cria o channel de saída, lança a goroutine executando uma função literal, e retorna o channel ao chamador. Ela é um “factory” para execuçaõ concomitante, “startando” a goroutine e retornando sua conexão.
A notação de função literal (linhas 12 a 16) permitem-nos construir uma função anônima e chamá-la no mesmo momento. Observe que a variável local ch está disponível para a função literal e existe até mesmo após o retorno do método generate.
A mesma alteração pode ser feita no método filter:
func filter(in chan int, prime int) chan int {
out := make(chan int);
go func() {
for {
if i := <-in; i % prime != 0 {
out <- i
}
}
}();
return out;
}
O loop principal da função sieve torna-se mais simples e clara como resultado, e enquanto estamos nela, vamos transformá-la em um “factory” também:
func sieve() chan int {
out := make(chan int);
go func() {
ch := generate();
for {
prime := <-ch;
out <- prime;
ch = filter(ch, prime);
}
}();
return out;
}
Agora a interface principal para a peneira de números primos é um channel de números primos:
func main() {
primes := sieve();
for {
fmt.Println(<-primes);
}
}
Abraços

[...] Números primos [...]
legal vlw…..
[...] Continua… [...]