Problema do Produtor e Consumidor
Aluno: Misael Alexandre

Codificação

Este problema, chamado de Produtor-Consumidor (também conhecido como o problema do buffer limitado), consiste em um conjunto de processos que compartilham um mesmo buffer. Os processos chamados produtores põem informação no buffer. Os processos chamados consumidores retiram informação deste buffer.

O mecanismo de comunicação entre processos utilizado foi a criação de uma pipe, a qual permite que um ou mais processos troquem informações entre si. O comando utilizado para a criação de novos processos foi fork(), que cria um processo filho retornado 0 e um processo pai retornando seu PID (Process ID).

O código do programa implementado pode ser visto abaixo.

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
 
#define CONSUMIR 0
#define ESCREVER 1
 
char* phrase="a";  //caracter a ser colocado no buffer
 
void main(){
 
int fd[2], bytesRead, prod, consum, i, j, tipo, int_rand, cont;
float rand;
 
char message[100];
 
printf("Entre com o numero de produtores:\n");
scanf("%d",&prod);
 
printf("Entre com o numero de consumidores:\n");
scanf("%d",&consum);
 
pipe(fd);
 
do{
 
     for(i=0; i<prod; i++){
 
    if(fork()==0){
       close(fd[CONSUMIR]); // Fecha a ponta não usada
 
       //Processo produtor produz 50 ítens
 
       for(j=0; j<50; j++){
           write(fd[ESCREVER],phrase,strlen(phrase));
           printf("Item produzido por %d (%d produzido pelo processo).\n",getpid(),j+1);
           sleep(1);  //Dorme ao fim de cada produção por 1 segundo
       }
 
       break;  //Ao termino da producao, pula pra fora do laco que de criacao de produtores
       close(fd[ESCREVER]);
    }
     }
 
     if(i!=prod) break;  //Se nao for o primeiro pai, pula pro fim do programa
 
     for(i=0; i<consum; i++){
 
        if(fork()==0){
       close(fd[ESCREVER]);  // Fecha a ponta não usada
       cont=1; // Contador de itens consumidos pelo processo
 
       //Processo consome itens
 
       while(1){
          bytesRead=read(fd[CONSUMIR], message, 1);
          if(bytesRead>0){
        printf("Lido %d item por %d (%d lido pelo processo): %s\n",bytesRead, getpid(), cont, message);
            cont++;
          }else printf("Nao ha nada para ler.");
          rand=0.0001*random();
          int_rand=rand;
          usleep(int_rand);   //Processo dorme por um tempo aleatorio ao fim de um consumo
       }
       break;
       close(fd[CONSUMIR]);
    }
     }
 
     //Processo pai produz algo antes de terminar
     write(fd[ESCREVER],phrase,strlen(phrase));
     printf("Item produzido por pai com pid %d.\n",getpid());
 
}while(0);
 
}

O programa inicia perguntando ao usuário o número de produtores e consumidores que ele deseja. Em seguida, é criada a pipe a ser utilizada. Inicia-se então um bloco de código com do. Isto foi feito para que, através de um break, um processo que já cumpriu sua função possa pular para o fim do programa, sendo assim encerrado.

Em seguida, são criados os processos produtores. Convencionou-se que todos os filhos criados pelo pai original, inicialmente, fossem produtores. Cada produtor, assim que criado, produzia 50 ítens, esperando 1 segundo entre cada produção. Ao fim do cumprimento de sua tarefa, através de dois breaks, este processo ia para o fim do programa, assim terminando.

Logo após foram criados os processos consumidores. Novamente convencionou-se que todos os filhos restantes criados pelo pai original fossem consumidores. Ao contrário dos produtores, que só produzem 50 ítems, os consumidores ficam tentando indefinidamente consumir. Esta diferença foi feita para que o programa fosse finito (não ficasse indefinidamente rodando). Quando o produtor não consegue mais consumir, ele apresenta uma mensagem.

A questão das condições de disputa, neste caso, já são resolvidas pela própria estrutura da pipe, ou seja, suas operações de escrita e leitura garantem exclusão mútua.

Testes

Foi feito então um teste para 15 produtores e 17 consumidores. A tela logo no início pode ser vista abaixo.

imagem111.jpg

Nesta tela acima, são criados todos os produtores (que já produzem algo) e depois os consumidores (que já consomem). Em seguida, começa a haver intercalamento entre produção e consumo, o que pode ser visto na figura abaixo, que representa a saída do programa no meio de sua execução.

imagem2.jpg

Por fim, a tela abaixo mostra o programa quando os processos consumidores não podem mais consumir e ficam mandando mensagens que não é possível consumir.

imagem3.jpg

A captura dos estados dos processos pode ser vista na figura abaixo. Notou-se que todos os processos estavam com S, ou seja, estavam dormindo.

proc_aux.jpg

Para se criar um processo zumbi, baseou-se na dica retirada do site //http://lists.debian.org/debian-user-portuguese/2005/05/msg00566.html//:

"Todo processo (filho) criado (por um pai) tem uma relação de 'paternindade'. O pai tem condições de saber se o filho ainda está vivo ou não, e algumas outras coisas bem simples (veja um bom livro de S.O. para mais detalhes). Por conta dessa 'vantagem', o pai sempre é informado qdo o filho morre - nesse momento, o filho morreu, mas o pai ainda nao tratou a msg, efetivamente recebendo a mensagem; nessa situação o processo filho é fica como zombie até que o pai reconheca essa 'msg' e só ai o filho pode morrer em paz… :-) "

Pensou-se então em bloquear o processo pai original. Para isso, mudou-se o código do final do programa, de modo que o processo pai fosse mais um consumidor. Os filhos produtores, ao encerrarem, não conseguem se comunicar com o pai (sleeping) e ficam zumbi. O trecho de código modificado pode ser visto abaixo.

...
 
  break;
       close(fd[CONSUMIR]);
    }
     }
 
// TRECHO MODIFICADO
     //Processo pai consome
     close(fd[ESCREVER]);  // Fecha a ponta não usada
       cont=1; // Contador de itens consumidos pelo processo
 
       //Processo consome itens
 
       while(1){
          bytesRead=read(fd[CONSUMIR], message, 1);
          if(bytesRead>0){
        printf("Lido %d item por %d (%d lido pelo processo): %s\n",bytesRead, getpid(), cont, message);
            cont++;
          }else printf("Nao ha nada para ler.");
          rand=0.0001*random();
          int_rand=rand;
          usleep(int_rand);   //Processo dorme por um tempo aleatorio ao fim de um consumo
       }
       close(fd[CONSUMIR]);
 
}while(0);
 
}

A tela que mostra os processos zumbis é:

aux_zumbi.jpg

Dificuldades

As dificuldades para a realização deste programa foram:

  • Entender bem o funcionamento dos processos após o fork, de forma que não aparecessem processos netos e fosse perdido o controle. Ou seja, entender que o processo deveria ser terminado após o término de sua tarefa.
  • Criação do processo zumbi. Houve dificuldade para ter a idéia de tornar o processo pai original um consumidor.
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License