quinta-feira, 7 de maio de 2009

Problema do Produtor-Consumidor utilizando pipes e processos

O problema do produtor-consumidor é um clássico problema que aborda a sincronização de múltiplos processos.

Este problema consiste de dois processos, o produtor e o consumidor, que compartilham uma memória (buffer). O produtor é responsável por gerar dados e colocá-los no buffer, repetindo estas ações inúmeras vezes. Enquanto isso, o consumidor remove/consome dados do buffer. Como o buffer possui um tamanho limitado, devemos garantir que o processo produtor não irá adicionar dados em um buffer cheio e que o consumidor não irá remover dados de um buffer vazio.

A solução para este problema consiste em colocar os processos para dormir enquanto uma das condições de parada valer. Se o buffer estiver cheio, o produtor dorme. Se o buffer estiver vazio, o consumidor dorme.

Um dos processos é responsável por acordar o outro processo. O produtor acorda o processo consumidor quando inserir dados no buffer; e o consumidor acorda o processo produtor quando remover dados do buffer.

Este problema torna-se interessante, pois soluções inadequadas resultam em um deadlock, ou seja, ambos processos estão dormindo.

Para aprender mais sobre este problema, visite: http://en.wikipedia.org/wiki/Producers-consumers_problem

Em nossa solução, podemos definir a quantidade de processos produtores e a quantidade de processos consumidores teremos. Definimos também a quantidade de pacotes que os produtores criarão e a capacidade máxima do pipe. Para criar os processos filhos, devemos utilizar a instrução fork() - http://www.opengroup.org/onlinepubs/000095399/functions/fork.html

Para produzir um novo produto, cada produtor levará um tempo randômico e para consumir cada produto, cada consumidor levará um tempo também randômico.

Os pipes são utilizados para permitir a comunicação inter-processos. Utilizando suas funções de read e write, conseguimos inserir um novo produto no pipe e remover um produto do pipe, sendo estas, funções realizadas por processos distintos.
Além disso, se desejamos escrever no pipe, devemos fechar a ponta de leitura. E, se desejamos ler do pipe, devemos fechar a ponta de escrita.
Maiores informações podem ser obtidas em: http://www.cs.cf.ac.uk/Dave/C/node23.html


Para compilar o programa e gerar o executável, utilizaremos:
gcc [[nome_programa.c]] -o [[nome_programa]] -lpthread
Para executar o programa criado, utilizaremos:
./[[nome_programa]]

Utilizamos o seguinte código:

//bibliotecas utilizadas...
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


//define os lados de leitura e escrita dos pipes...
#define READ 0
#define WRITE 1

//define a quantidade de produtos produzidos pelos produtores e a quantidade de pacotes que um pipe suporta
#define MAX_PROD_PACKS 100
#define MAX_PIPE_PACKS 30

//define a quantidade de produtores e de consumidores
#define PRODUCERS 2
#define CONSUMERS 2


//definimos uma estrutura para os produtos a serem produzidos, armazenando o id do pai
typedef struct prod prod;

struct
prod{int prodid;pid_t producerid;};

int
fd[2];

int
n;

//Prototipos...
void consume_prod(prod*);
prod produce_prod(int);


void
producer()
{

int
qtdade;
prod newprod;

close(fd[READ]); // Fecha o lado de leitura. Lado nao utilizado

for
(qtdade=0;qtdade<MAX_PROD_PACKS;qtdade++){

newprod = produce_prod(qtdade); //produz novo item
printf("Novo item produzido pelo processo %d! \n", getpid());

write(fd[WRITE],&newprod,sizeof(prod)); // insere novo item no pipe
}

close (fd[WRITE]); // Fecha o lado utilizado
}

void
consumer()
{


int
qtdade;
int
resp;
prod consumeprod;
close (fd[WRITE]); /* Fecha o lado de leitura que nao eh utilizado*/

while
( 1 ){

resp = read(fd[READ],&consumeprod,sizeof(prod));
if
(resp==-1){
printf("Erro na leitura do pipe\n");
}


else if
(resp==0){
printf("Pipe estah vazio... \n");
}

else
{

consume_prod(&consumeprod);
}
}

close (fd[READ]); /* Fecha o lado utilizado*/

}


prod produce_prod(int n){
prod newprod;
int
timetoproduce;

timetoproduce = rand()%7;
newprod.prodid = n;

newprod.producerid = getpid();
printf("Processo %d produzindo um novo produto\n",getpid());

sleep(timetoproduce);
return
newprod;
}


void
consume_prod(prod * t){

int
timetoconsume;
timetoconsume=rand()%5;
printf("Processo %d consumindo produto %d do Produtor %d\n",getpid(),t->prodid,t->producerid);

sleep(timetoconsume);
}


void
new_producer(){
int
newprocess;

newprocess = fork();
if
(newprocess==-1){
printf("Erro na criacao do processo produtor\n");
}


else if
(newprocess==0){
producer();
exit(0);
}


return
;
}

void
new_consumer(){
int
newprocess;
newprocess = fork();

if
(newprocess==-1){
printf("Erro na criacao do processo consumidor\n");
}

else
{

consumer();
exit(0);
}

}


int
main(){

pipe(fd); //cria o pipe

int
i;

/* Criação de Processos Produtores */

for
( i=0;i<PRODUCERS;i++){

new_producer();
}

/* Criação de Processos consumidores */

for
(i=0;i<CONSUMERS;i++){

new_consumer();
}

//o processo pai será um processo consumidor

exit(0);
}



Assim, obtivemos o seguinte resultado:

andersonaiziro@Aaiziro:~/Documents$ ./prod-cons
Processo 6743 produzindo um novo produto
Processo 6744 produzindo um novo produto
Novo item produzido pelo processo 6743!
Processo 6743 produzindo um novo produto
Processo 6745 consumindo produto 0 do Produtor 6743
Novo item produzido pelo processo 6744!
Processo 6744 produzindo um novo produto
Processo 6746 consumindo produto 0 do Produtor 6744
Novo item produzido pelo processo 6743!
Processo 6743 produzindo um novo produto
Processo 6746 consumindo produto 1 do Produtor 6743
Novo item produzido pelo processo 6744!
Processo 6744 produzindo um novo produto
Processo 6742 consumindo produto 1 do Produtor 6744
Novo item produzido pelo processo 6743!
Processo 6743 produzindo um novo produto
Processo 6746 consumindo produto 2 do Produtor 6743
Novo item produzido pelo processo 6744!
Processo 6744 produzindo um novo produto
Processo 6745 consumindo produto 2 do Produtor 6744
Novo item produzido pelo processo 6743!
Processo 6743 produzindo um novo produto
Processo 6746 consumindo produto 3 do Produtor 6743
Novo item produzido pelo processo 6744!
Processo 6744 produzindo um novo produto
Processo 6745 consumindo produto 3 do Produtor 6744
Novo item produzido pelo processo 6743!
Processo 6743 produzindo um novo produto
Processo 6746 consumindo produto 4 do Produtor 6743
Novo item produzido pelo processo 6744!
Processo 6744 produzindo um novo produto
Processo 6742 consumindo produto 4 do Produtor 6744
Novo item produzido pelo processo 6743!
Processo 6743 produzindo um novo produto
Processo 6742 consumindo produto 5 do Produtor 6743
Novo item produzido pelo processo 6744!
Processo 6744 produzindo um novo produto
Processo 6746 consumindo produto 5 do Produtor 6744
Novo item produzido pelo processo 6743!
Processo 6743 produzindo um novo produto
Processo 6742 consumindo produto 6 do Produtor 6743
Novo item produzido pelo processo 6744!
Processo 6744 produzindo um novo produto
Processo 6746 consumindo produto 6 do Produtor 6744
Novo item produzido pelo processo 6743!
Processo 6743 produzindo um novo produto
Processo 6746 consumindo produto 7 do Produtor 6743
Novo item produzido pelo processo 6744!
Processo 6744 produzindo um novo produto
Processo 6745 consumindo produto 7 do Produtor 6744
Novo item produzido pelo processo 6743!
Processo 6743 produzindo um novo produto
Processo 6742 consumindo produto 8 do Produtor 6743
Novo item produzido pelo processo 6744!
Processo 6744 produzindo um novo produto
Processo 6745 consumindo produto 8 do Produtor 6744
Novo item produzido pelo processo 6743!
Processo 6743 produzindo um novo produto
Processo 6746 consumindo produto 9 do Produtor 6743
Novo item produzido pelo processo 6744!
Processo 6744 produzindo um novo produto
Processo 6745 consumindo produto 9 do Produtor 6744
Novo item produzido pelo processo 6743!
Processo 6743 produzindo um novo produto
Novo item produzido pelo processo 6744!
Processo 6744 produzindo um novo produto
Processo 6742 consumindo produto 10 do Produtor 6743
Processo 6742 consumindo produto 10 do Produtor 6744

Verificado o funcionamento do programa, vamos criar os processos zumbis!

Para realizar tal tarefa, vamos inicialmente, identificar uma situação em que temos os processos zumbis. Ao realizar o comando fork(), criamos um processo filho. A morte do processo pai, acarreta na morte do processo filho. Por outro lado, a morte do processo filho deve ser informada ao processo pai. Se o processo pai estiver bloqueado, então o processo filho torna-se um processo zumbi.

Para fazer esta situação acontecer, vamos fazer o processo pai como um processo consumidor e vamos criar uma situação em que não temos mais produtos no pipe. Assim, o processo pai ficará bloqueado e o processo filho não conseguirá informar ao pai sobre seu término, tornando-se um processo zumbi.

Para verificar a situação dos processos, vamos utilizar o seguinte comando:

ps -ax

O resultado desta simulação foi:


A existência de processos zumbi é indicada pela letra Z+.

Nenhum comentário:

Postar um comentário