Laboratório 12 - 13/6/2001 e 20/6/2001

No laboratório de hoje, propomos um outro padrão de criação e de comunicação entre threads, chamado de pipe,

O exercício proposto aqui é bem mais complexo do que os anteriores. Gostaria que vocês pensassem bastante *antes* de começar a criar código, para projetarem as rotinas e estruturas de dados necessárias ao programa.

Construa um programa que imprima os n primeiros números primos, onde n é um parâmetro de linha de comando. O programa deve usar a idéia do crivo de Eratóstenes, criando um thread para testar a divisibilidade de "candidatos a primos" por cada primo já encontrado.

primos:        2         3         4         5       ...   
             ----- s   ----- s   ----- s   -----           
      3->     %3?  -->  %5?  -->  %7?  -->  %11? --> ...   
      5      -----     -----     -----     -----
      7
      9
      11
      13
      ...

Uma thread no programa deve funcionar como um gerador de números ímpares. As threads "testadoras" devem ser criadas dinamicamente, a medida que alguma outra thread tem necessidade de passar um valor adiante.

Cada thread no pipe deverá se comunicar com a seguinte através de um buffer de uma posição e duas variáveis de condição. Uma estrutura contendo esses três objetos, um pthread_mutex e ainda o número desse thread na ordem do pipe deve ser criada dinamicamente antes de criar a thread, e seu endereço deve ser passado como parâmetro para a nova thread. O valor inicial contido no buffer deve ser 0.

Cada thread testadora se comporta da seguinte maneira. O primeiro número recebido no buffer tem status especial, pois é o número D que a thread usará para testar divisibilidades dos números seguintes. Esse primeiro número não é passado adiante. A testadora deve imprimir na tela o valor desse número. A seguir, a thread testadora fica esperando aparecerem números no buffer, e a cada vez que aparece um número c, ela testa sua divisibilidade por D, Caso c não seja divisível por D, ele deve ser passado para a testadora seguinte (é um candidato a primo). Caso a testadora seguinte ainda não exista, deve ser criada nesse instante.

As duas variáveis de condição são necessárias para controlar o acesso ao buffer. Na relação entre duas threads, uma se comporta como produtora e a outra como consumidora. Após consumir um item, a consumidora pode sempre atribuir zero ao buffer, para permitir que a produtora teste esse valor.

Tente escrever seu programa de forma organizada, com rotinas para produzir e consumir itens parametrizadas pela estrutura de controle. A criação de nova testadora também merece uma rotina em separado.

Não se preocupe inicialmente com a terminação "organizada" do programa. Depois de escrever na tela os n primeiros primos, as threads podem ficar fazendo trabalho inútil. Mas não devem ser criadas novas threads e nem impressos mais do que n valores. Para isso serve o número da thread que é passado como parâmetro em sua criação. Cada thread deve testar se ela é a última no pipe para decidir se deve ou não passar números adiante. Depois que isso estiver funcionando, pense como o programa poderia ser estruturado para terminar efetivamente.