Programa 4 Fabio Imada

retornar

Problema dos Leitores e Escritores

Introdução

Continuando o estudo de threads e semáforos e adicionando o mutex, será implementado o problema dos leitores e escritores.
O problema modela o acesso a um banco de dados, onde podem haver vários leitores ao mesmo tempo, mas apenas um escritor de cada vez.
Portanto, se quem está acessando o banco de dados é um leitor, outros leitores podem entrar a vontade. Mas se um escritor está acessando, nenhum outro processo pode acessar o banco.
Para a solução foram utilizados, além dos semáforos, mutexes (explicados abaixo).

Mutex

Mutex é a abreviação de MUTual EXclusion, ou exclusão mútua e serve para proteger áreas críticas, evitando condições de corrida. Anteriormente estávamos utilizando semáforos binários para tal fim, no entanto, não precisamos saber o valor da contagem no mutex, por isso o mutex é uma estrutura mais enxuta para o mesmo fim.
A proteção da região crítica ocorre através das funções:

  • Lock - Quando um processo/thread ganha direito de acessar uma região crítica, ele utiliza o lock para impedir que outro processo a acesse ao mesmo tempo. Quando o outro processo chega, o lock está ativado e o processo aguarda sua desativação.
  • Unlock - Ao terminar de acessar a região crítica, o processo a libera com a função unlock, que também avisa os processos que a estão aguardando que já podem fazer o uso dela.

O mutex está incluso na biblioteca da linguagem C "pthread.h" e está definido pelo tipo pthread_mutex_t. Ele é inicializado com pthread_mutex_init, destruído com pthread_destroy e responde às funções pthread_mutex_lock e pthread_mutex_unlock.

Desenvolvimento

Considerando apenas as especificaćões do problema, caso haja um fluxo constante de leitores, os escritores nunca teriam chance de começar a escrever.
Por esse motivo, o problema foi resolvido de forma diferente: a partir do momento que um escritor chega, os leitores que chegam posteriormente aguardam o escritor. Dessa forma, não obtém-se o máximo de processamento paralelo possível, mas evita-se que um processo espere indefinidamente.
Além disso, foi necessário colocar um tempo de "processamento" dentro do banco de dados e um tempo de espera na área fora do mutex, para que os processos não entrassem e imediatamente saíssem (impossibilitando eventuais condićões de corrida), ou que somente um processo utiliza-se o banco de dados (liberando o mutex e retomando-o imediatamente depois).

Conteúdo do arquivo leitores.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#define N_LEITORES 4
#define N_ESCRITORES 2
#define TRUE 1
#define FALSE 0
 
pthread_mutex_t mutex;
sem_t db;
int rc = 0;
 
void ler_dados(int i){
    printf("Lendo... leitor #%d\n",i);
    usleep(rand()%300000);
    printf("Pronto... leitor #%d\n",i);
}
 
void utilizar_dados(){
    usleep(rand()%1000000);
}
 
/* Para leitura, podem acessar o banco de dados quantos leitores precisarem.
 * Quando o primeiro leitor entra, o semáforo de acesso ao banco recebe um
 * down, esse down só recebe up quando o último leitor sai.
 * O mutex foi utilizado para que os leitores que estão lendo o banco de dados ao
 * mesmo tempo não corrompam a variável de contagem.*/
void leitor(int i)
{
    while (TRUE) {
        pthread_mutex_lock(&mutex);
        rc = rc + 1;
        if (rc == 1) sem_wait(&db);
        pthread_mutex_unlock(&mutex);
        ler_dados(i);
        pthread_mutex_lock(&mutex);
        rc -= 1;
        if (rc == 0) sem_post(&db);
        pthread_mutex_unlock(&mutex);
        utilizar_dados();
    }
}
 
void criar_dados(){
    usleep(rand()%1000000);
}
 
void escrever_dados(int i){
    printf("Escrevendo... escritor #%d\n",i);
    usleep(rand()%500000);
    printf("Terminado... escritor #%d\n",i);
}
 
/* Apenas um escritor pode acessar o banco de dados de cada vez. */
void escritor(int i)
{
    int qtd;
    for (qtd = 0 ; qtd < 20 ; qtd++) {
        criar_dados();
        sem_wait(&db);
        escrever_dados(i);
        sem_post(&db);
    }
    pthread_exit("Escritor terminado.\n");
}
 
int main(){
    int res, res_parc ,i;
    pthread_t thread_reader[N_LEITORES], thread_writer[N_ESCRITORES];
 
    /* Inicializando semaforo e mutex */
    res = 0;
    res_parc = pthread_mutex_init(&mutex, NULL);
    res += res_parc;
    res_parc = sem_init(&db, 0, 1);
    res += res_parc;
    if (res != 0) {
        perror("Erro na inicializacao do semaforo ou mutex.");
        exit(EXIT_FAILURE);
    }
    /* Inicializando threads */
    for(res = 0 , i = 0 ; i < N_ESCRITORES ; i++){
        res_parc = pthread_create(&thread_writer[i], NULL, (void*)escritor, (int*)(i+1));
        res += res_parc;
    }
    for(i = 0 ; i < N_LEITORES ; i++){
        res_parc = pthread_create(&thread_reader[i], NULL, (void*)leitor, (int*)(i+1));
        res += res_parc;
    }
    if (res != 0) {
        perror("Erro em criacao de threads\n");
        exit(EXIT_FAILURE);
    }
    /* Juntando threads (leitores continuam indefinidamente) */
    for(res = 0 , i = 0 ; i < N_ESCRITORES ; i++){
        res_parc = pthread_join(thread_writer[i], NULL);
        res += res_parc;
    }
    if (res != 0) {
        perror("Erro em join threads\n");
        exit(EXIT_FAILURE);
    }
    exit(EXIT_SUCCESS);
}

Screenshots

A tela abaixo apresenta o comando para compilar o programa e sua execução.

leitores.png

retornar

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License