Laboratório de CES-33 - Lívia Palomo Vidal - COMP10

INTRODUÇÃO

Esta página foi criada para expor alguns programas desenvolvidos para a disciplica de Sistemas Operacionais (S.O.) do curso de Engenharia da Computação do ITA.
Os programas trabalham com threads, semáforos e pipes em C no ambiente LINUX e mostram como funcionam na prática estes tópicos abordados em aula.
Os problemas resolvidos pelos programas são problemas clássicos do estudo de processos e threads e suas soluções evitam problemas de corrida, deadlocks e starvation. O estudo de soluções para tais questões é essencial para qualquer um que queira entender os princípios básicos de S.O.

PROBLEMAS

1) Jantar dos Filósofos

O problema clássico do jantar dos filósofos consiste no seguinte:

- Existem 5 filósofos em uma mesa redonda, cada um com 1 prato de macarrão a frente.
- Existem 5 garfos na mesa, posicionados entre os pratos de forma que cada filosofo tem um garfo a sua esquerda e um a sua direita.
- Um filosofo só consegue comer macarrão usando 2 garfos ao mesmo tempo.
- Um filosofo pode estar em um dos três estados: "pensando", "comendo" ou "com fome".
- Um filósofo com fome tenta pegar os garfos para passar para o estado "comendo"

O desafio é implementar um programa em C que simule o problema usando uma thread para cada filósofo e dê uma solução que evite race conditions ou starvation.

Segue abaixo a implementação deste problema. O programa usa as bibliotecas "semaphore.h" e "pthread.h" para implementar semáforos e threads. Os semáforos controlam o acesso ao array que armazena o estado de cada filósofo (região crítica) e serve também para bloquear ou acordar filósofos que estão tentando comer.

Na simulação:
- cada filósofo foi nomeado através de um índice de 0 a 4;
- os filósofos estão sentados na sequência 0,1,2,3 e 4;
- os estados estão simbolizados por inteiros;
- os atos de "pensar" e "comer" demoram um número aleatório de segundos entre 0 e 10.

As funções do programa estão devidamente comentadas, dispensando maiores explicações

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
 
#define N    5 // Número de filósofos na mesa
#define ESQ    (i+N-1)%N
#define DIR    (i+1)%N
#define PENSA    0
#define FOME    1
#define COME    2
 
sem_t mutex; //semáforo binário para controlar acesso à região crítica
sem_t s[N]; //semáforo binário para bloquear/acordar filósofo que tentou pegar garfos e não conseguiu
int estado[N]; //array de inteiros onde são armazenados o estado de cada filósofo. Pertence à região crítica.
 
//Funções usadas no programa:
void filosofo(int); 
void por_garfos(int);
void pegar_garfos(int);
void teste(int);
void pensar(int);
void comer(int);
 
int main(){
 
//A função main cria 5 threads diferentes, uma para cada filósofo.
//Cada thread é acionada com a função "filósofo(int i)" que simula o comportamento do filósofo de índice i.
//O fim do programa fica atrelado ao fim das threads devido às chamadas de "pthread_join".
 
    sem_init(&mutex,0,1);
    pthread_t t0;
    pthread_t t1;
    pthread_t t2;
    pthread_t t3;
    pthread_t t4;
 
    int num_thread[5];
    int i;
    for(i=0; i<5; i++){
        num_thread[i]=i;
    }
 
    pthread_create ( &t0, NULL, (void *) filosofo, (int*)(num_thread[0]));
    pthread_create ( &t1, NULL, (void *) filosofo, (int*)(num_thread[1]));
    pthread_create ( &t2, NULL, (void *) filosofo, (int*)(num_thread[2]));
    pthread_create ( &t3, NULL, (void *) filosofo, (int*)(num_thread[3]));
    pthread_create ( &t4, NULL, (void *) filosofo, (int*)(num_thread[4]));
 
    pthread_join(t0, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);
}
 
void filosofo(int i){
 
//Função que simula o comportamento de um filósofo, chamada pelas 5 threads de main.
//Chama as funções que simulam os atos de pensar, pegar garfos da mesa, comer e repor garfos na mesa.
 
    while (1){
        pensar(i);
        pegar_garfos(i);
        comer(i);
        por_garfos(i);
    }
}
 
void pensar(int i){
 
//Função que simula um determinado filósofo pensando, ao escrever uma mensagem na tela de comando 
//e esperar um tempo entre 0 e 10s.
 
    printf("Filosofo %d comecou a pensar\n", i);
    sleep((rand()%10));
}
 
void comer(int i){
 
//Função que simula um determinado filósofo comendo, ao escrever uma mensagem na tela de comando
//e esperar um tempo entre 0 e 10s.
 
    printf("Filosofo %d comecou a comer\n", i);
    sleep(rand()%10);
}
 
void pegar_garfos(int i){
 
//Função que simula um determinado filósofo tentando pegar garfos na mesa.
 
    sem_wait(&mutex); //entra na região crítica
    estado[i]= FOME;  //estado "fome" indica que o filosofo está tentando pegar garfos
    teste(i); //chama função que testa se o filósofo consegue pegar o garfo e comer
    sem_post(&mutex); //sai da região crítica
    sem_wait(&s[i]); //bloqueia caso o filósofo não tenha conseguido comer
}
 
void por_garfos(int i){
 
//Função que simula um determinado filósofo devolvendo os garfos à mesa.
 
    sem_wait(&mutex); //entra na região crítica
    estado[i]= PENSA; //filósofo que retorna os garfos volta ao estado "pensando"
    teste(ESQ);//chama função que testa se o filósofo a direita consegue pegar o garfo e comer
    teste(DIR);//chama função que testa se o filósofo a esquerda consegue pegar o garfo e comer
    sem_post(&mutex); //sai da região crítica
}
 
void teste(int i){
 
//Função que testa se os garfos estão livres para o filósofo. 
//Caso estejam, o filósofo passa ao estado "comendo" e o semáforo é acionado.
 
    if(estado[i]==FOME && estado[ESQ]!=COME && estado[DIR]!=COME){
        estado[i]= COME;
        sem_post(&s[i]);
    }
}

RESULTADO DE SIMULAÇÕES

Foi realizado uma simulação e o resultado segue abaixo. Note que não há starvation e que dois vizinhos nunca comem ao mesmo tempo, como esperado.

Filosofo 0 comecou a pensar
Filosofo 1 comecou a pensar
Filosofo 2 comecou a pensar
Filosofo 3 comecou a pensar
Filosofo 4 comecou a pensar
Filosofo 0 comecou a comer
Filosofo 3 comecou a comer
Filosofo 0 comecou a pensar
Filosofo 1 comecou a comer
Filosofo 3 comecou a pensar
Filosofo 4 comecou a comer
Filosofo 4 comecou a pensar
Filosofo 3 comecou a comer
Filosofo 3 comecou a pensar
Filosofo 1 comecou a pensar
Filosofo 2 comecou a comer
Filosofo 0 comecou a comer
Filosofo 0 comecou a pensar
Filosofo 4 comecou a comer
Filosofo 4 comecou a pensar
Filosofo 1 comecou a comer
Filosofo 3 comecou a comer
Filosofo 2 comecou a pensar
Filosofo 1 comecou a pensar
Filosofo 0 comecou a comer
Filosofo 0 comecou a pensar
Filosofo 0 comecou a comer
Filosofo 0 comecou a pensar
Filosofo 3 comecou a pensar
Filosofo 4 comecou a comer
Filosofo 2 comecou a comer
Filosofo 4 comecou a pensar
Filosofo 0 comecou a comer
Filosofo 0 comecou a pensar
Filosofo 4 comecou a comer
Filosofo 2 comecou a pensar
Filosofo 1 comecou a comer
Filosofo 1 comecou a pensar
Filosofo 2 comecou a comer
Filosofo 4 comecou a pensar
Filosofo 0 comecou a comer
Filosofo 3 comecou a comer
Filosofo 2 comecou a pensar
Filosofo 3 comecou a pensar
Filosofo 2 comecou a comer
Filosofo 2 comecou a pensar
Filosofo 4 comecou a comer
Filosofo 1 comecou a comer
Filosofo 0 comecou a pensar
Filosofo 1 comecou a pensar
Filosofo 1 comecou a comer
Filosofo 1 comecou a pensar
Filosofo 2 comecou a comer
Filosofo 4 comecou a pensar
Filosofo 0 comecou a comer
Filosofo 2 comecou a pensar
Filosofo 3 comecou a comer
Filosofo 3 comecou a pensar
Filosofo 3 comecou a comer

2) Problema da Barbearia

O problema clássico da barbearia consiste no seguinte:

- Existe 1 barbeiro que pode estar cortando cabelo de um cliente ou descansando, caso não haja clientes na barbearia;
- Existem cadeiras de espera na barbearia para acomodar até 5 clientes esperando para cortar o cabelo;
- Um barbeiro pode estar cortando o cabelo ou esperando chegarem clientes, caso a barbearia esteja vazia;
- Um cliente pode estar cortando o cabelo ou esperando nas cadeiras;
- Caso um cliente chegue em um momento no qual as 5 cadeiras de espera estejam ocupadas, ele vai embora.

O desafio é implementar um programa em C que simule o problema usando uma thread para cada cliente e uma para o barbeiro e dê uma solução que evite race conditions.

Segue abaixo a implementação deste problema. O programa usa as bibliotecas "semaphore.h" e "pthread.h" para implementar semáforos e threads. Os semáforos controlam o acesso à variável que armazena o número de clientes esperando para cortar o cabelo (região crítica). Servem também para bloquear ou acordar o barbeiro e os clientes que estejam esperando.

Na simulação:
- existem 7 clientes, nomeados segundo um índice de 1 a 7, que ficam constantemente tentando ter o cabelo cortado;
- existe 1 barbeiro na barbearia, que fica constantemente tentando cortar o cabelo de algum cliente;
- a quantidade de clientes esperando é armazenada na variável "esperando";
- um corte de cabelo demora sempre 3s.
- um cliente demora um número aleatório de segundos entre 0 e 15 para retornar à barbearia depois de ter o cabelo cortado.

As funções do programa estão devidamente comentadas, dispensando maiores explicações.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
 
#define CADEIRAS 5
 
typedef sem_t semaforo;
 
semaforo clientes; //Semaforo que bloqueia caso barbeiro tente começar um corte
// e não haja clientes em espera
semaforo barbeiros; //Semaforo que bloqueia caso um cliente tente ter o cabelo cortado
// e o barbeiro não esteja disponível
semaforo mutex; //Semáforo binário que controla acesso à região crítica
int esperando = 0; //Variável que controla o número de clientes na fila. Pertence à região crítica.
 
//Funções usadas no programa:
void cortar_cabelo();
void ter_cabelo_cortado(int);
void barbeiro();
void cliente(int);
 
int main(){
 
//A função main cria 7 threads diferentes, uma para cada cliente, além de uma para o barbeiro.
//Cada thread é acionada com a função "cliente(int i)" que simula o comportamento do cliente de índice i. 
//A thread do barbeiro é acionada com a função "barbeiro()" que simula o comportamento do único barbeiro.
//O fim do programa fica atrelado ao fim das threads devido às chamadas de "pthread_join".
 
    sem_init(&clientes,0,0);
    sem_init(&barbeiros,0,0);
    sem_init(&mutex,0,1);
 
    pthread_t t0;
    pthread_t t1;
    pthread_t t2;
    pthread_t t3;
    pthread_t t4;
    pthread_t t5;
    pthread_t t6;
    pthread_t t7;
 
    int num_thread[8];
    int i;
    for(i=1; i<8; i++){
        num_thread[i]=i;
    }
 
    pthread_create ( &t0, NULL, (void *) barbeiro, NULL);
    pthread_create ( &t1, NULL, (void *) cliente, (int*)(num_thread[1]));
    pthread_create ( &t2, NULL, (void *) cliente, (int*)(num_thread[2]));
    pthread_create ( &t3, NULL, (void *) cliente, (int*)(num_thread[3]));
    pthread_create ( &t4, NULL, (void *) cliente, (int*)(num_thread[4]));
    pthread_create ( &t5, NULL, (void *) cliente, (int*)(num_thread[5]));
    pthread_create ( &t6, NULL, (void *) cliente, (int*)(num_thread[6]));
    pthread_create ( &t7, NULL, (void *) cliente, (int*)(num_thread[7]));
 
    pthread_join(t0, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);
    pthread_join(t5, NULL);
    pthread_join(t6, NULL);
    pthread_join(t7, NULL);
}
 
void barbeiro(){
 
//Função que simula o comportamento do barbeiro, chamada por uma das threads de main.
//Ela simula a espera por um cliente, usando ponteiros, e o corte;
 
    while(1){
        sem_wait(&clientes); //bloqueia se não existem clientes esperando
        sem_wait(&mutex); //entra ma região crítica para reduzir o número de clientes esperando,
        // armazenado em "esperando"
        esperando = esperando - 1;
        printf("Barbeiro chamou cliente. Existem %d clientes esperando\n", esperando);
        sem_post(&barbeiros); //ativa cliente esperando ao indicar pelo semáforo que o barbeiro 
        //está pronto para cortar
        sem_post(&mutex); //libera região crítica
        cortar_cabelo(); //chama a função que simula que o barbeiro está cortando o cabelo
 
    }
}
 
void cliente(int i){
 
//Função que simula o comportamento de um cliente, chamada pelas demais threads de main.
//Ela simula a verificação de disponibilidade de cadeiras, a espera pelo corte e o corte de cabelo.
//Após sair por falta de cadeira ou ter o cabelo cortado, o cliente retorna à barbearia após 
//um período aleatório entre 0 e 15s.
 
    while(1){
        sem_wait(&mutex); //entra ma região crítica para verificar o número de clientes esperando, 
        //armazenado em "esperando"
        if(esperando<CADEIRAS) { //entra no estado de espera, caso tenha cadeiras
            esperando = esperando + 1; //aumento o valor de "esperando", que armazena o número 
            //de clientes na espera
            printf("Cliente %d sentou na fila. Existem %d clientes esperando\n", i, esperando);
            sem_post(&clientes); //acorda o barbeiro, ao indicar pelo semáforo que o cliente está 
            //esperando pelo corte
            sem_post(&mutex); //libera região crítica
            sem_wait(&barbeiros); //bloqueia caso o barbeiro esteja ocupado e espera
            ter_cabelo_cortado(i); //chama a função que simula que o cliente está tendo o cabelo cortado
        }
        else {
            printf("Cliente %d foi embora por falta de cadeira. Existem %d clientes esperando\n", i, esperando);
            sem_post(&mutex); //vai embora sem corte e libera região crítica, caso não tenha cadeiras
        }
        sleep(rand()%15); //retorna depois de alguns segundos (aleatório)
    }
}
 
void cortar_cabelo(){
 
//Função que simula que o barbeiro está cortando cabelo, ao escrever uma mensagem 
//na tela de comando.
 
    printf("Barbeiro começou a cortar cabelo de um cliente\n");
    sleep(3);
}
 
void ter_cabelo_cortado(int i){
 
//Função que simula um determinado cliente tendo o cabelo cortado, ao escrever uma mensagem 
//na tela de comando e esperar 3s.
 
    printf("Cliente %d começou a ter o cabelo cortado\n", i);
    sleep(3);
}

RESULTADO DE SIMULAÇÔES

Foi realizado uma simulação. O resultado segue abaixo. Veja que os clientes e o barbeiro se comportam como esperado: o barbeiro está sempre ocupado enquanto há clientes e os clientes formam uma fila de espera de no máximo 5, que sempre são atendidos pelo barbeiro e liberam lugar na fila.

Cliente 1 sentou na fila. Existem 1 clientes esperando
Cliente 2 sentou na fila. Existem 2 clientes esperando
Cliente 3 sentou na fila. Existem 3 clientes esperando
Cliente 4 sentou na fila. Existem 4 clientes esperando
Barbeiro chamou cliente. Existem 3 clientes esperando
Cliente 2 começou a ter o cabelo cortado
Barbeiro começou a cortar cabelo de um cliente
Cliente 5 sentou na fila. Existem 4 clientes esperando
Cliente 6 sentou na fila. Existem 5 clientes esperando
Cliente 7 foi embora por falta de cadeira. Existem 5 clientes esperando
Barbeiro chamou cliente. Existem 4 clientes esperando
Cliente 1 começou a ter o cabelo cortado
Barbeiro começou a cortar cabelo de um cliente
Cliente 2 sentou na fila. Existem 5 clientes esperando
Barbeiro chamou cliente. Existem 4 clientes esperando
Cliente 3 começou a ter o cabelo cortado
Barbeiro começou a cortar cabelo de um cliente
Barbeiro chamou cliente. Existem 3 clientes esperando
Cliente 4 começou a ter o cabelo cortado
Barbeiro começou a cortar cabelo de um cliente
Barbeiro chamou cliente. Existem 2 clientes esperando
Barbeiro começou a cortar cabelo de um cliente
Cliente 5 começou a ter o cabelo cortado
Cliente 7 sentou na fila. Existem 3 clientes esperando
Barbeiro chamou cliente. Existem 2 clientes esperando
Barbeiro começou a cortar cabelo de um cliente
Cliente 6 começou a ter o cabelo cortado
Cliente 1 sentou na fila. Existem 3 clientes esperando
Barbeiro chamou cliente. Existem 2 clientes esperando
Cliente 2 começou a ter o cabelo cortado
Barbeiro começou a cortar cabelo de um cliente
Cliente 3 sentou na fila. Existem 3 clientes esperando
Cliente 6 sentou na fila. Existem 4 clientes esperando
Cliente 4 sentou na fila. Existem 5 clientes esperando
Barbeiro chamou cliente. Existem 4 clientes esperando
Cliente 7 começou a ter o cabelo cortado
Barbeiro começou a cortar cabelo de um cliente
Barbeiro chamou cliente. Existem 3 clientes esperando
Barbeiro começou a cortar cabelo de um cliente
Cliente 1 começou a ter o cabelo cortado
Cliente 5 sentou na fila. Existem 4 clientes esperando
Barbeiro chamou cliente. Existem 3 clientes esperando
Barbeiro começou a cortar cabelo de um cliente
Cliente 3 começou a ter o cabelo cortado
Cliente 1 sentou na fila. Existem 4 clientes esperando
Barbeiro chamou cliente. Existem 3 clientes esperando
Barbeiro começou a cortar cabelo de um cliente
Cliente 6 começou a ter o cabelo cortado

3) Leitores & Escritores

O problema clássico dos Leitores e Escritores modela o acesso a um base de dados. Uma solução deve cumprir os seguintes requisitos:

- Existem N leitores e M escritores que atuam sobre uma mesma base de dados;
- Se vários leitores estiverem lendo a base ao mesmo tempo, não há problema;
- Um escritor deve ter acesso exclusivo à base, para evitar concorrência entre escritores ou leituras desatualizas e mal-sucessidas;

O desafio é implementar um programa em C que simule o problema usando uma thread para cada escritor/leitor e dê uma solução que satisfaça os critérios acima e evite problemas de corrida.

Segue abaixo a implementação deste problema. O programa usa as bibliotecas "semaphore.h" e "pthread.h" para implementar semáforos e threads. Neste problema, é usado também o semáforo do tipo mutex para controlar o acesso à variável "contador_leitores", que armazena o número de leitores lendo a base em um determinado momento. O outro semáforos controlam o acesso à base de dados. Serve também para bloquear ou acordar leitores e escritores que estejam esperando acesso.

Na simulação:
- a base de dados é simulada da forma mais simples possível: uma string global, cujo valor é atualizado pelos escritores e lido pelos leitores;
- existem 3 escritores e 2 leitores, nomeados segundo um índice de 0 a 5, que ficam constantemente lendo e escrevendo na variável global;
- quando um leitor tem o acesso à variável, qualquer outro leitor que tentar lê-la também terá acesso, mas nunca um escritor;
- um escritor só tem acesso à variável quando não há mais leitores com acesso a ela;

As funções do programa estão devidamente comentadas, dispensando maiores explicações.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
 
#define N 50
 
typedef sem_t semaforo; 
typedef pthread_mutex_t mutex;
 
mutex mtx; //semáforo que controla acesso à variável "contador_leitores"
semaforo db; //semáforo que controla acesso à base de dados. No caso, à variável global "dado"
 
int contador_leitores = 0; //contador de leitores com acesso à base de dados
char dado[N]; //variável global que simula a base de dados compartilhada
 
//Funções usadas no programa:
void leitor(int);
void escritor(int);
void ler_arquivo(int,char*);
void usar_dados(int, char*);
void preparar_dados(char*, int);
void escrever_arquivo(char*);
void function(int);
 
int main(){
 
//A função main cria 5 threads diferentes, 2 para escritores e 3 para leitores.
//Cada thread é acionada com a função "escritor(int i)" ou "leitor(int i), que 
//simulam o comportamento de um escritor e de um leitor, respectivamente.
//O fim do programa fica atrelado ao fim das threads devido às chamadas de "pthread_join".
 
    strcpy(dado,"Mensagem original"); //Mensagem inicial contida na base de dados
    pthread_mutex_init(&mtx, NULL); //Semáforo que controla "contador_leitores" iniciado como setado
    sem_init(&db,0,1); //Semáforo que controla acesso à base de dados iniciado como setado
 
    pthread_t t0;
    pthread_t t1;
    pthread_t t2;
    pthread_t t3;
    pthread_t t4;
 
    int num_thread[5];
 
    int i;
    for(i=0; i<5; i++){
        num_thread[i]=i;
    }    
 
    pthread_create ( &t0, NULL, (void *) escritor, (int*)(num_thread[0]));
    pthread_create ( &t1, NULL, (void *) leitor, (int*)(num_thread[1]));
    pthread_create ( &t2, NULL, (void *) leitor, (int*)(num_thread[2]));
    pthread_create ( &t3, NULL, (void *) escritor, (int*)(num_thread[3]));
    pthread_create ( &t4, NULL, (void *) escritor, (int*)(num_thread[4]));
 
    pthread_join(t0, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);
    exit(0);
 
}
 
void leitor(int i) {
 
//Função simula o comportamento de um leitor. Inicialmente, tenta acesso à variável que 
//conta o número de leitores lendo/esperando para ler o base de dados. 
//A seguir, se junta aos leitores que leem a variável ou, caso não haja outros leitores, 
//tenta adquirir acesso à base e espera se o base estiver sendo //atualizada. Após ler a base, 
//realiza o processo contrário: 
//- pede acesso à variável contadora, 
//- reduz o número de leitores, 
//- libera acesso à base caso seja o último leitor a acabar de ler, 
//- libera o acesso à variável contadora, 
//- usa o dado lido
//- e espera um número aleatório de segundos entre 0 e 10 para ler novamente a base de dados
 
    char mensagem[N];
    while(1) {
 
        pthread_mutex_lock(&mtx); //adquiri acesso à variável contadora
        contador_leitores = contador_leitores + 1; //incrementa contagem de leitores com intenção de ler
        if (contador_leitores == 1){ //caso seja o primeiro leitor a chegar, tenta acesso à base
            sem_wait(&db); //bloqueia acesso à base caso haja um escritor atualizando-na
            printf("Leitor %d bloqueou a base de dados para escritores\n",i);
        }
        pthread_mutex_unlock(&mtx);//libera acesso à variável contadora
        ler_arquivo(i, mensagem); //lê a base de dados
        pthread_mutex_lock(&mtx); //adquiri acesso à variável contadora
        contador_leitores = contador_leitores - 1; //decrementa contagem de leitores com intenção de ler
        if (contador_leitores == 0){ //se for o último leitor a ler a base, libera acesso a ela
            sem_post(&db);
            printf("Leitor %d liberou a base de dados\n",i);
        }
        pthread_mutex_unlock(&mtx);//libera acesso à variável contadora
        usar_dados(i, mensagem);//usa dado lido na base
        sleep(rand()%10);//espera para nova leitura
    }
}
 
void escritor(int i) {
 
//Função que simula o comportamento de um escritor:
//- prepara uma mensagem, 
//- pede acesso à base de dados, 
//- atualiza a base com a mensagem preparada
//- libera acesso à base de dados
//- e espera um número aleatório de segundos entre 0 e 10 para atualizar novamente a base de dados
 
    char mensagem[N];
 
    while(1) {
        preparar_dados(mensagem,i);
        sem_wait(&db); //pede acesso à base de dados e bloqueia caso haja leitores ou outro escritor com acesso a ela
        escrever_arquivo(mensagem);
        sem_post(&db); //libera acesso à base de dados
        sleep(rand()%10); //espera para nova atualização
    }
}
 
void ler_arquivo(int i, char* mensagem){
 
//Função que lê a base de dados e armazena o dado no local apontado pelo ponteiro 
//passado como parâmetro.
//A leitura de um arquivo ou banco de dados demora mais do que a leitura de uma variável. 
//Por isso, foi simulada esta condição utilizando a função "sleep(int)" com um valor aleatório 
//entre 0 e 10 segundos. 
//Além disso, para que seja possível perceber o que está acontecendo, é escrita uma 
//mensagem avisando qual leitor está lendo o arquivo.
 
    printf("Leitor %d lendo arquivo...\n", i);
    sleep(rand()%10);
    strcpy(mensagem, dado);
}
 
void usar_dados(int i, char* mensagem){
 
//Função que usa o dado lido. Este dado é armazenado no local apontado pelo 
//ponteiro passado como parâmetro. O uso do dado é simulado imprimindo-o na tela, 
//junto com uma mensagem que avisa qual leitor fez uso do dado.
 
    printf("Mensagem usada pelo leito %d: %s\n", i, mensagem);
 
}
 
void preparar_dados(char* mensagem, int i){
 
//Função que prepara uma mensagem para atualização da base de dados e a salva no local 
//apontado pelo ponteiro passado como parâmetro. Esta mensagem usa o índice do escritor 
//que a chamou para que seja possível saber a origem na mensagem colocada na base.
 
    sprintf(mensagem,"Mensagem do escritor %d\n", i);
}
 
void escrever_arquivo(char* mensagem){
 
//Função que atualiza a base de dados com a mensagem apontada pelo ponteiro passado como parâmetro.
//Como a atualização de um arquivo ou banco de dados é mais demorada do que a atualização de 
//uma variável, foi colocada uma chamada para o função "sleep(int)" que simulada uma demora 
//de 0 a 10 segundos.
 
    printf("Dado agora é: %s\n", mensagem);
    sleep(rand()%10);
    strcpy(dado,mensagem);
 
}

RESULTADO DE SIMULAÇÔES

Foi realizada um simulação, cujo resultado se encontra abaixo.
Note que:

- os escritores somente atualizam quando o último leitor libera acesso à base de dados;
- o primeiro leitor a chegar libera acesso à base para todos os outros leitores;
- os leitores estão sempre atualizados, ou seja, têm a última versão do dado na base.

liviapv@ubuntu:~/Desktop/Blog$ gcc readers_writers.c -o rd -lpthread
liviapv@ubuntu:~/Desktop/Blog$ ./rd

Leitor 1 bloqueou a base de dados para escritores
Leitor 1 lendo arquivo...
Leitor 2 lendo arquivo...
Mensagem usada pelo leito 1: Mensagem original
Leitor 2 liberou a base de dados
Mensagem usada pelo leito 2: Mensagem original
Dado agora é: Mensagem do escritor 0
Dado agora é: Mensagem do escritor 3
Dado agora é: Mensagem do escritor 4
Leitor 1 bloqueou a base de dados para escritores
Leitor 2 lendo arquivo...
Leitor 1 lendo arquivo...
Mensagem usada pelo leito 2: Mensagem do escritor 4
Leitor 2 lendo arquivo...
Mensagem usada pelo leito 1: Mensagem do escritor 4
Leitor 1 lendo arquivo...
Mensagem usada pelo leito 2: Mensagem do escritor 4
Leitor 2 lendo arquivo...
Mensagem usada pelo leito 1: Mensagem do escritor 4
Leitor 2 liberou a base de dados
Mensagem usada pelo leito 2: Mensagem do escritor 4
Dado agora é: Mensagem do escritor 0
Dado agora é: Mensagem do escritor 3
Dado agora é: Mensagem do escritor 4
Dado agora é: Mensagem do escritor 4
Dado agora é: Mensagem do escritor 0
Leitor 1 bloqueou a base de dados para escritores
Leitor 1 lendo arquivo...
Leitor 2 lendo arquivo...
Mensagem usada pelo leito 2: Mensagem do escritor 0
Leitor 2 lendo arquivo...
Mensagem usada pelo leito 1: Mensagem do escritor 0
Leitor 2 liberou a base de dados
Mensagem usada pelo leito 2: Mensagem do escritor 0
Dado agora é: Mensagem do escritor 4
Dado agora é: Mensagem do escritor 3
Dado agora é: Mensagem do escritor 0
Leitor 1 bloqueou a base de dados para escritores
Leitor 1 lendo arquivo...
Leitor 2 lendo arquivo...
Mensagem usada pelo leito 1: Mensagem do escritor 0
Leitor 1 lendo arquivo...
Mensagem usada pelo leito 2: Mensagem do escritor 0
Dado agora é: Mensagem do escritor 3
Leitor 1 liberou a base de dados
Mensagem usada pelo leito 1: Mensagem do escritor 0
Dado agora é: Mensagem do escritor 4

4) Produtores & Consumidores

Em computação, muitas vezes dois processos diferentes precisam trocar informação entre si. Um processo, chamado produtor, envia um dado ou mensagem para um processo consumidor. Esta comunicação pode ser feita através de pipes.

Podem ser usados pipes genéricos, sem nome, os chamados unnamed pipes. Neste caso, uma forma de fazer com que dois ou mais processos utilizem esse pipe é criando processos filho através da chamada "fork()".

Outra forma de lidar com o problema é usar pipes nomeados. Neste caso, existe um nome, uma referência para o pipe, que pode ser passada para os processos e, assim, fazer com que os processos usem o pipe de comunicação apropriado. Um named pipe é persistente no sistema e existe fora do processo onde foi criado. Desta forma, é possível que outros processos o enxerguem e o utilizem, desde que usado nome de referência.

Optei por trabalhar com pipes nomeados, pois me pareceu de mais fácil compreensão. Neste caso, os processos consumidores e produtores podem ser criados na linha de comando e podemos verificar a comunicação entre eles passando mensagens na forma de string entre um produtor que as escreve e um produtor que as lê e imprime na tela.

CONSUMIDOR:

O código abaixo é o de um processo consumidor. Ele lê mensagens na saída de um pipe, que é indicada pelo ponteiro descritor de leitura do pipe criado, até que ele se esvazie e as imprime na tela.

#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h> 
#define N 100
 
int main(){
 
    int fd; //inteiro que armazena o descritor de leitura do pipe
    char str[N]; //string para armazenar a mensagem consumida
    mkfifo("myPipe", 0660); //cria um pipe nomeado ou retorna EEXIST caso o pipe já exista
 
    fd = open ("myPipe", O_RDONLY); //abre o pipe para leitura e armazena o descritor de leitura em fd
 
    while(consumir(fd, str)) //consume os itens do pipe até que ele se esvazie
        printf("Consumidor %d recebeu mensagem de %s\n", getpid(), str); 
        //consume a mensagem, ou seja, mostra que recebeu a mensagem ao imprimir uma mensagem de aviso
 
    close (fd); //fecha a saída do pipe
}
 
consumir(int fd,char* str){ 
 
//Função que consome um item do pipe. No caso, uma string. 
//Se o pipe estiver vazio, o processo é bloquado.
 
    int n;
    do{ 
        n = read(fd, str, 1); 
    } while(n > 0 && *str++ != 0); //lê a string até que ela acabe
 
    if (n>0) return 1; //retorna 1 se o pipe não estiver vazio
    return 0; //retorna 0 se o pipe estiver vazio
}

PRODUTOR

O código abaixo é o de um processo produtor. Ele produz uma mensagem que contém o seu número PID que o identifica, abre a entrada do pipe criado pelos produtores, e coloca a mensagem no pipe.

#include <stdio.h>
#include <fcntl.h>
#include <string.h>
 
#define N 100
 
int main() {
 
    int fd; //inteiro que armazena o descritor de escrita do pipe
    int tamanho, i; //inteiro que armazena o tamanho da mensagem a ser colocada no pipe
    char message[N]; //string para a mensagem a ser criada e colocada no pipe
    sprintf(message, "Produtor de PID = %d", getpid()); //cria uma mensagem com o número do seu PID    
    tamanho= strlen(message) + 1; //armazena o tamanho da mensagem
 
    do{ 
        fd = open ("myPipe", O_WRONLY); //tenta abrir a entrada do pipe criado pelo consumidor
        if(fd== -1) sleep(1); 
        //e espera para tentar novamente caso o pipe não exista ou ocorra algum outro erro na abertura
    } while(fd == -1);
 
    write(fd, message, tamanho); //escreve a mensagem no pipe
    close (fd); //fecha a entrada do pipe
}

RESULTADOS DE SIMULAÇÕES

Realizei duas simulações com vários produtores e consumidores. Os resultados seguem abaixo.

liviapv@ubuntu:~/Desktop/Blog$ ./prod & ./cons & ./prod & ./prod &./cons & ./cons & ./prod & ./cons

[1] 9933
[2] 9934
Consumidor 9934 recebeu mensagem de Produtor de PID = 9935
[3] 9935
[4] 9936
Consumidor 9937 recebeu mensage de Produtor de PID = 9936
[5] 9937
[6] 9938
Consumidor 9938 recebeu mensagem de Produtor de PID = 9939
[7] 9939
Consumidor 9940 recebeu mensagem de Produtor de PID = 9933
[2]   Done                    ./cons
[3]   Done                    ./prod
[4]   Done                    ./prod
[5]   Done                    ./cons
[6]-  Done                    ./cons
[7]+  Done                    ./prod
liviapv@ubuntu:~/Desktop/Blog$ ./cons &./prod & ./cons & ./prod & ./prod &./prod & ./prod & ./prod & ./cons

[1] 10040
[2] 10041
Consumidor 10040 recebeu mensagem de Produtor de PID = 10041
[3] 10042
[4] 10043
[5] 10044
Consumidor 10042 recebeu mensagem de Produtor de PID = 10043
[6] 10045
[7] 10046
[8] 10047
Consumidor 10048 recebeu mensagem de Produtor de PID = 10046
Consumidor 10048 recebeu mensagem de Produtor de PID = 10045
Consumidor 10048 recebeu mensagem de Produtor de PID = 10047
Consumidor 10048 recebeu mensagem de Produtor de PID = 10044
[1]   Done                    ./cons
[2]   Done                    ./prod
[3]   Done                    ./cons
[4]   Done                    ./prod
[5]   Done                    ./prod
[6]   Done                    ./prod
[7]-  Done                    ./prod
[8]+  Done                    ./prod
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License