Experiências

sexta-feira, 8 de maio de 2009

Processos Zombies

Um processo zombie é aquele processo filho que morreu sem o pai saber. Foi utilizado o seguinte código para gerar vários processos zombie:

#include <stdio.h>

int
main(){

char
msg[100];
int
id=1;
int
count=0;

while
(id && count<10){
id=fork();
count++;
}


if
(!id){
sprintf(msg,"kill %i",getpid());
system(msg);
}
else{

while
(1){
sleep(1);
system("ps -es");
}
}


return
0;
}


O que acontece no código?

Primeiramente são criados diversos processos filhos. Em seguida eles se suicidam com a chamada de kill para o próprio process id. Abaixo está a saída do programa:

Produtores e consumidores

O problema dos produtores e consumidores foi implementado segundo o seguinte código:

#include <stdio.h>
#include <string.h>
#define READ 0
#define WRITE 1


void
main ()
{

int
fd [2], bytesRead;

char
message[100];
char
phrase[100];
int
id=1;

int
count=0;
pipe (fd);

while
(id && count<=20){

count++;
id=fork();
}


if
(id == 0){
while
(1){

sleep(1);
sprintf(phrase,"%i",getpid());
write (fd[WRITE],phrase, strlen (phrase) + 1);
}
}

else
{

count=0;
while
(id && count<=1){
count++;

id=fork();
}


if
(!id){
while
(1){
sleep(1);
bytesRead = read (fd[READ], message, 100);
printf ("Processo %i recebe item do processo %s\n",getpid(),message);
}
}
}

}


O que esse programa faz é, primeiramente, criar vários processos filhos utilizando-se a função fork(). Esses primeiros processos serão os produtores. Segundamente, são criados mais processos filhos, os quais serão os consumidores. O item produzido é o próprio process id do produtor.

Abaixo, será apresentado um exemplo com poucos produtores e muitos consumidores.



Agora, será apresentado um exemplo com muitos produtores e poucos consumidores.



Como os consumidores não conseguem consumir todo o conteúdo do pipe, começam a surgir resquícios, representados pelos algarismos solitários.

Durante a execução do programa, foi realizada a chamada a ps -es para se verificar o estado dos processos:

quinta-feira, 7 de maio de 2009

Leitores e escritores

A vida computacional ocorre num mundo cheio de informação. Muita dessa informação é guardada em bancos de dados. Quando as informações do BD estão imutáveis é uma boa hora de se ler o seu conteúdo. Quando há leitores acessando o BD não é "educado" alterar as informações dele, deve-se, portanto, esperar que todos os leitores terminem seu trabalho. Quando o BD estiver livre, o escritor pode tomá-lo para si e fazer as modificações que desejar.

Esse problema é chamado de problema dos leitores e escritores. O seguinte programa simula essa situação.

#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>

#define NUM_READERS 10
#define T_READER 1
#define T_WRITER 5
#define TRUE 1
#define FALSE 0

pthread_mutex_t mutex;
pthread_mutex_t db;
int
rc = 0;

void
read_data_base(int i){
printf("Leitores acessando a base de dados: %i\n",i);
}


void
write_data_base(){
printf("Escritor escrevendo na base de dados\n");
}



void
reader(void){

while
(TRUE) {
sleep(T_READER);
pthread_mutex_lock(&mutex);
rc = rc + 1;
if
(rc == 1) pthread_mutex_lock(&db);
pthread_mutex_unlock(&mutex);
read_data_base(rc);
pthread_mutex_lock(&mutex);
rc = rc - 1;
if
(rc == 0) pthread_mutex_unlock(&db);
pthread_mutex_unlock(&mutex);
}
}



void
writer(void){

while
(TRUE) {
sleep(T_WRITER);
pthread_mutex_lock(&db);
write_data_base();
pthread_mutex_unlock(&db);
}
}


void
*reader_thread(void *arg){
reader();
}


void
*writer_thread(void *arg){
writer();
}


int
main(){
int
count=0;
pthread_t r_thread[NUM_READERS];
pthread_t w_thread;

pthread_mutex_init(&db,NULL);
pthread_mutex_init(&mutex,NULL);

pthread_create(&w_thread,NULL,writer_thread,NULL);

for
(count=0;count<NUM_READERS;count++) pthread_create(&r_thread[count],NULL,reader_thread,NULL);

pthread_join(w_thread,NULL);

for
(count=0;count<NUM_READERS;count++) pthread_join(r_thread[count],NULL);

return
0;
}


Nesse programa, são usados mutexes que são semáforos especiais. O mutex é um semáforo que possui apenas dois valores: 0 e 1. O mutex db garante que o escritor só acesse o BD quando estiver livre e o mutex mutex permite uma contagem confiável de leitores, pois faz a exclusão mútua no acesso à variável rc (readers count).

Uma simulação do problema é mostrado na figura abaixo.



Nesse caso, há 10 leitores e um escritor. Percebe-se que a utilização do BD é, na maior parte do tempo, feita por leitores. Pode existir uma situação em que a frequencia de acesso de leitores não permite que o escritor tenha acesso ao banco de dados.

A barbearia

A barbearia na "vida" computacional possui um barbeiro dorminhoco, uma cadeira para o barbeiro cortar o cabelo do cliente e algumas cadeiras de espera. Se um cliente adentra a barbearia e o barbeiro está dormindo, ele é acordado e o cliente, atendido. Caso contrário o cliente senta em uma das cadeiras de espera. Se não há cadeiras de espera livres, o cliente simplesmente vai embora. Quando o barbeiro termina o corte de um cliente, dá uma olhada nas cadeiras de espera, se houver clientes, ele escolhe um e o atende. Caso contrário, volta a dormir até aparecerem novos clientes.

Para garantir que clientes não furem fila, que o barbeiro não durma para sempre, que os clientes em espera sejam atendidos e apenas um por vez são usados semáforos.

O código a seguir implementa a barbearia da vida computacional.

#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>

#define CHAIRS 5
#define TRUE 1
#define FALSE 0
#define T_BARB 3
#define T_CUST 1

sem_t customers;
sem_t barbers;
sem_t mutex;

int
waiting = 0;

void
*barbeiro_thread(void *arg);
void
*cliente_thread(void *arg);


void
barber_sleeping(){
printf("Barbeiro dormindo...\n");
}


void
cut_hair(){
printf("Barbeiro ocupado...\n");
}


void
customers_waiting(int i){
printf("Clientes aguardando: %i\n",i);
}


void
customer_leaves(){
printf("Cliente vai embora...\n");
}


void
get_haircut(){
printf("Cliente tem o cabelo aparado...\n");
}



void
barber(void){

while
(TRUE) {
barber_sleeping();
sem_wait(&customers);
sem_wait(&mutex);
waiting = waiting - 1;
sem_post(&barbers);
sem_post(&mutex);
cut_hair();
sleep(T_BARB);
}
}



void
customer(void){

sem_wait(&mutex);

if
(waiting < CHAIRS) {

waiting = waiting + 1;
sem_post(&customers);
sem_post(&mutex);
customers_waiting(waiting);
sem_wait(&barbers);
get_haircut();
}
else {
customer_leaves();
sem_post(&mutex);
}
}



void
*barbeiro_thread(void *arg){
barber();
}



void
*ccliente_thread(void *arg){
customer();
}



void
*cliente_thread(void *arg){
pthread_t cc_thread;

while
(1){
sleep(T_CUST);
pthread_create(&cc_thread,NULL,ccliente_thread,NULL);
}
}


int
main(){
pthread_t b_thread;
pthread_t c_thread;

sem_init(&customers,0,0);
sem_init(&barbers,0,0);
sem_init(&mutex,0,1);

pthread_create(&b_thread,NULL,barbeiro_thread,NULL);
pthread_create(&c_thread,NULL,cliente_thread,NULL);

pthread_join(b_thread,NULL);
pthread_join(c_thread,NULL);

return
0;
}

O semáforo barbers serve para sinalizar se o barbeiro está ou não ocupado e garante que os clientes não furem fila. O semáforo customers serve para sinalizar se há clientes. O semáforo mutex garante o atendimento de apenas um cliente por vez e que todos em espera serão atendidos. O barbeiro e os clientes são threads.

Façamos a simulação de um barbeiro preguiçoso, que demora para atender os clientes, sendo que os clientes estão chegando rapidamente à barbearia. Temos o seguinte resultado:



Percebe-se que as cadeiras de espera enchem rapidamente e vários clientes vão embora.

Agora, façamos a simulação de um barbeiro eficiente, que atende rapidamente os clientes. Temos o seguinte resultado:



Percebe-se que há, no máximo, um cliente na cadeira de espera.

O jantar dos filósofos


O problema citado anteriormente referente ao jantar de família tem um semelhante na vida computacional. É o jantar dos filósofos.

Imaginemos cinco filósofos que fazem apenas 3 coisas: pensar, sentir fome e comer. Eles vivem ao redor de uma mesa circular com cinco pratos e um garfo ao lado de cada prato, de forma que há, no total, cinco garfos. Para poder comer, o filósofo precisa pegar, necessariamente, o garfo do lado direiro e o garfo do lado esquerdo. Se todos os filósofos tentarem pegar os garfos adjacentes aos seus respectivos pratos, nenhum deles conseguirá dois garfos, e ninguém comerá.

Uma solução é restringir a um filósofo por vez a tentativa de pegar os garfos. Para isso a "vida" computacional possui um construto chamado semáforo, que diz se um filósofo pode tentar pegar os garfos ou deve esperar a sua vez.

Um filósofo que tenta pegar os garfos vê se os seus colegas adjacentes estão comendo, caso ambos não estejam comendo, os garfos de que precisa estão livres. Pode, assim, pegar os garfos e comer.

O jantar dos filósofos pode ser simulado com o seguinte programa em linguagem C.

#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>

#define N 5
#define LEFT (i+N-1)%N
#define RIGHT (i+1)%N
#define THINKING 0
#define HUNGRY 1
#define EATING 2
#define TRUE 1
#define FALSE 0


int
state[N];
sem_t mutex;
sem_t s[N];



void
*filosofo_thread(void *arg);


void
print_name(int i){
switch
(i){
case
0: printf("Aristoteles"); break;
case
1: printf("Socrates"); break;
case
2: printf("Platao"); break;
case
3: printf("Descartes"); break;
case
4: printf("Pitagoras"); break;
}
}


void
think(int i){
print_name(i);
printf(
" esta pensando...\n");
}


void
eat(int i){
print_name(i);
printf(
" esta comendo...\n");
}


void
hungry(int i){
print_name(i);
printf(
" esta com fome...\n");
}



void
test(i){
if
(state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING) {
state[i] = EATING;
sem_post(&s[i]);
}
}



void
take_forks(int i){
sem_wait(&mutex);
state[i] = HUNGRY;
hungry(i);
test(i);
sem_post(&mutex);
sem_wait(&s[i]);
}



void
put_forks(i){
sem_wait(&mutex);
state[i] = THINKING;
test(LEFT);
test(RIGHT);
sem_post(&mutex);
}



void
philosopher(int i){
while
(TRUE) {
think(i);
take_forks(i);
eat(i);
put_forks(i);
}
}



void
*filosofo_thread(void *arg){
philosopher((
int)arg);
}



int
main(){
int
count=0;
pthread_t f_thread[N];


for
(count=0;count<N;count++) state[count]=THINKING;
for
(count=0;count<N;count++) sem_init(&s[count],0,0);
sem_init(&mutex,
0,1);

for
(count=0;count<N;count++)(&f_thread[count],NULL,filosofo_thread,( pthread_createvoid *)count);
for
(count=0;count<N;count++) pthread_join(f_thread[count],NULL);

return
0;
}


A execução do código acima produz a seguinte saída:


No programa escrito acima, cada um dos filósofos é uma thread. E são usados 6 semáforos no total, um dos semáforos (mutex) regula o acesso ao estado de cada filósofo, e mais cinco semáforos (s) para controlar as tentativas de pegar os garfos.

A vida real e a vida computacional

A vida real é, digamos assim, perfeita. Quando sentamos à mesa com nossa família não temos que nos degladiar para utilizar os talheres. Podemos nos levantar e buscar outros talheres na cozinha, ou compartilhá-los com nossos familiares. Se precisamos cortar o cabelo podemos ir ao nosso barbeiro de confiança e seremos atendidos uma hora ou outra. Agricultores produzem os alimentos que compramos, mas se a produção não consegue suprir a necessidade, dá-se um "jeitinho". Já na vida computacional, isso mesmo, na "vida" dentro do computador, não ocorre da mesma maneira. Se imaginarmos essa "vida" composta por pequenos seres humanóides, pode-se dizer que essas "pessoinhas" são bem cabeças-duras. Elas seguem regras muito rígidas, e às vezes têm problemas mal resolvidos entre elas, o que requer a intervenção de nós, seres humanos, ou, mais especificamente, engenheiros de computação.

Começando do começo

Aqui é o começo!

Seguidores