Sunday, May 10, 2009

Comunicação entre processos: "Produtores e Consumidores" utilizando pipes e processos

Nos posts anteriores utilizamos threads para solucionar alguns clássicos problemas IPC. Neste post utilizaremos pipes e forks para solucionar o problema dos "Produtores e Consumidores", demonstrando como pode ser feita a comunicação entre processos.

Produtores colocam dados em um buffer, enquanto consumidores retiram dados do buffer. O problema surge quando produtores tentarem colocar dados em um buffer já cheio ou, analogamente, consumidores tentarem retirar dados de um buffer vazio.

A solução aqui apresentada utiliza pipes, permitindo que processos produtores se comuniquem com consumidores. Mais especificamente, um único pipe é criado e todos os processos o utilizam para retirar ou inserir dados. Considerando que as operações são thread-safe, a solução garante o bom comportamento do programa, como demonstrado pelos testes.

Os processos gerados a partir do processo pai executarão o código de Producer ou Consumer.
A fim de permitir a comunicação entre os mesmos, inicialmente criamos um pipe (executando na linha de comando -- $ mkfifo thePipe

A seguir, apresentamos o código-fonte dos dois arquivos.


// producer.c

#include <stdio.h>
#include <fcntl.h>
#include <string.h>

int main () {
int fd, messageLen, i;
char message [100]; /* Prepare message */
sprintf (message, "Hello from producer PID %d parent is %d",
getpid (), getppid());
messageLen = strlen (message) + 1;
do /* Keep trying to open the file until successful */
{
fd = open ("thePipe", O_WRONLY); /* Open named pipe for writing */
if (fd == -1) sleep (1); /* Try again in 1 second */
} while (fd == -1);
for (i = 1; i <= 3; i++) /* Send three messages */ { write (fd, message, messageLen); /* Write message down pipe */ sleep (3); /* Pause a while */ } close (fd); /* Close pipe descriptor */ return 1; }



// consumer.c

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>

int readLine (int fd,char *str);
int main () {
int fd;
char str[100];
fd = open ("thePipe", O_RDONLY); /* Open it for reading */
while (readLine (fd, str)) /* Display received messages */
printf ("%s\n", str);
close (fd); /* Close pipe */
sleep(3);
return 1;
}

/* Read a single NULL-terminated line into str from fd */
/* Return 0 when the end-of-input is reached and 1 otherwise */
int readLine (int fd,char *str) {
int n;
do /* Read characters until NULL or end-of-input */
{
n = read (fd, str, 1); /* Read one character */
} while (n > 0 && *str++ != 0 );
return (n > 0); /* Return false if end-of-input */
}


Finalmente, apresentamos o programa que será executado e ordenará a geração dos processos-filho.


//producer_consumer.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>

void create_producer();
void create_consumer();

int main() {

int index;

create_producer();
create_producer();
create_consumer();

return 1;

}

void create_producer() {
printf("Produtor criado.\n");
int pid = fork();
if (pid == 0) {
execlp("./producer","producer",NULL);
}
}

void create_consumer() {
printf("Consumidor criado.\n");
int pid = fork();
if (pid == 0) {
execlp("./consumer","consumer",NULL);
}
}





Note que quando o processo filho completa sua execução mas ainda tem uma entrada em sua process table (para permitir que o pai leia seu status de saída) este passa a se denominar zombie.
Podemos simular tal situação se colocarmos o processo-pai para dormir por um longo período e o processo-filho apenas executando exit(0). Enquanto o pai dormir, o filho será um zombie. Note que isto é diferente de processo-órfão.

Finalmente, um simples teste.



bash-3.2$ ./consumer & ./producer &./producer &
[1] 2526
[2] 2527
[3] 2528
Hello from producer PID 2527
Hello from producer PID 2528
Hello from producer PID 2527
Hello from producer PID 2528
Hello from producer PID 2527
Hello from producer PID 2528

[1] Exit 1 ./consumer
[2]- Exit 1 ./producer
[3]+ Exit 1 ./producer
bash-3.2$

No comments:

Post a Comment