Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Readers-Writers

Você já usou um read-write lock pronto: vários leitores juntos, um escritor sozinho. Agora vamos construir um, com as peças que já temos na mão. O exercício vale porque mostra que o rwlock não é mágica de sistema operacional: é um mutex, um semáforo e um contador arranjados de um jeito esperto, e ver o arranjo explica de onde vem a starvation que o capítulo do rwlock mencionou.

A ideia central é assimétrica. Um escritor precisa de exclusão total, então basta um semáforo binário, escrita, que ele adquire antes de escrever e libera depois. Leitores são o caso interessante: muitos podem entrar juntos, mas coletivamente eles precisam manter os escritores de fora enquanto pelo menos um estiver lendo. A sacada é fazer o primeiro leitor a chegar trancar o semáforo de escrita, e o último a sair destrancar. Os leitores do meio nem tocam nesse semáforo, e é por isso que entram todos juntos.

Para saber quem é o primeiro e quem é o último, os leitores mantêm um contador, readcount. Incrementar e testar esse contador é uma seção crítica (dois leitores não podem, ambos, achar que são o primeiro), então ele mesmo é protegido por um mutex. O Código 10-2 monta tudo isso:

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

int readcount = 0;                                 // quantos leitores estão dentro
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;   // protege o readcount
sem_t escrita;                                     // 1 = livre pra escrever

int dado = 0;

void *leitor(void *arg) {
    int id = *(int *)arg;
    for (int i = 0; i < 2; i++) {
        pthread_mutex_lock(&mtx);
        readcount++;
        if (readcount == 1) sem_wait(&escrita);    // o primeiro leitor tranca a escrita
        pthread_mutex_unlock(&mtx);

        printf("leitor %d leu %d\n", id, dado);    // vários podem estar aqui juntos
        usleep(50000);

        pthread_mutex_lock(&mtx);
        readcount--;
        if (readcount == 0) sem_post(&escrita);    // o último leitor destranca
        pthread_mutex_unlock(&mtx);
    }
    return NULL;
}

void *escritor(void *arg) {
    int id = *(int *)arg;
    for (int i = 0; i < 2; i++) {
        sem_wait(&escrita);                        // exclusivo: espera nenhum leitor
        dado++;
        printf("escritor %d escreveu %d\n", id, dado);
        usleep(50000);
        sem_post(&escrita);
    }
    return NULL;
}

int main(void) {
    sem_init(&escrita, 0, 1);
    pthread_t l[3], w[2]; int lid[3]={0,1,2}, wid[2]={0,1};
    for (int i=0;i<3;i++) pthread_create(&l[i],NULL,leitor,&lid[i]);
    for (int i=0;i<2;i++) pthread_create(&w[i],NULL,escritor,&wid[i]);
    for (int i=0;i<3;i++) pthread_join(l[i],NULL);
    for (int i=0;i<2;i++) pthread_join(w[i],NULL);
    printf("fim\n");
    return 0;
}

Código 10-2: Um read-write lock montado com contador, mutex e um semáforo.

$ cc main.c -o main -pthread
$ ./main
leitor 0 leu 0
leitor 1 leu 0
leitor 2 leu 0
leitor 2 leu 0
leitor 0 leu 0
leitor 1 leu 0
escritor 0 escreveu 1
escritor 0 escreveu 2
escritor 1 escreveu 3
escritor 1 escreveu 4
fim

Olhe a ordem da saída. Os seis “leu” aconteceram primeiro, todos vendo dado = 0, com vários leitores dentro ao mesmo tempo. Só depois que o último leitor saiu é que os escritores conseguiram entrar. Não foi coincidência: enquanto chegava leitor, o readcount nunca voltava a zero, o sem_post(&escrita) nunca era chamado, e os escritores ficavam presos no sem_wait. Com leitores chegando sem parar, um escritor poderia esperar pra sempre.

Essa é a starvation de escritor de que falamos ao ver a API do rwlock, agora visível na construção: a preferência por leitores não é uma decisão que alguém tomou por cima, é uma consequência direta de como as peças foram encaixadas. Esta é a solução original de Courtois, e ela favorece leitores por construção. Dar preferência a escritores, ou alternar de forma justa, exige mais contadores e mais semáforos, arranjados pra barrar novos leitores assim que um escritor manifesta interesse.1


  1. A solução com preferência a escritores usa um segundo semáforo pra segurar leitores na entrada quando há escritor esperando, mais um contador de escritores análogo ao readcount. É o mesmo tipo de raciocínio, só que aplicado aos dois lados. ↩