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.