Um Tutorial para a Linguagem de Programação Go – parte 8

Posted by ALOVasconcelos on Dec 7, 2009 in Dicas, Tutoriais, laboratório |

[Voltar ao menu]

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);
        }
    }

Continua…

Abraços

Tags: , , ,

3 Comments

Leave a Reply

XHTML: You can use these tags:' <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Copyright © 2008-2010 ALOVasconcelos All rights reserved.
Desk Mess Mirrored v1.5.1 theme from BuyNowShop.com.