Race condition e sincronização de threads com mutex
Para entendermos o problema que a sincronização com mutex resolve, vamos primeiro trazer o problema.
A ideia é escrever um programa que cria 5 threads, onde cada thread faz um incremento de 100 mil vezes em uma variável (counter) compartilhada entre todas as threads.
Ao fim do programa, queremos que o counter tenha um valor total de 500.000, ou seja, 5 threads x 100.000. Certo? Vamos então à implementação do programa:
#include <stdio.h>
#include <pthread.h>
#define NUM_THREADS 5
#define INCREMENTS 100000
int counter = 0; // Variável compartilhada entre as threads
void* increment(void* arg) {
for (int i = 0; i < INCREMENTS; i++) {
counter++; // Incrementa a variável compartilhada
}
return NULL;
}
int main() {
pthread_t threads[NUM_THREADS];
// Cria as threads
for (int i = 0; i < NUM_THREADS; i++) {
pthread_create(&threads[i], NULL, increment, NULL);
}
// Aguarda as threads terminarem
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
printf("Valor final do counter compartilhado: %d (esperado: %d)\n", counter, NUM_THREADS * INCREMENTS);
return 0;
}
Tudo ok até aqui, pois os comentários no código são auto-explicativos. Vamos executar o programa e:
Valor final do counter compartilhado: 282989 (esperado: 500000)
Eita! Note que o valor final do counter ficou bem abaixo do esperado. Experimente rodar outras vezes e repare que a cada execução, o valor final será diferente.
Isto, senhoras e senhores, é a maravilha da concorrência. Não temos controle algum sobre a ordem e execução das tarefas!
Sim, vou repetir isso inúmeras vezes neste guia kk
Race condition
O que temos aqui é examente um cenário de race condition, ou condição de corrida, onde o valor final de um recurso compartlihado depende da ordem de execução das threads. Em outras palavras, este recurso precisa ser sincronizado entre as threads.
E para isto, recorremos ao uso de travas - locks - que, para nossa sorte, a biblioteca padrão implementa uma abstração chamada mutex (exclusão mútua), através do uso da função pthread_mutex_lock
.
Mutex
O uso de mutex em C é muito simples. Tudo o que precisamos é de criar uma variável compartilhada que irá representar o mutex:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
E em volta da mutação do recurso, fazemos o bloqueio e desbloqueio do mutex:
pthread_mutex_lock(&mutex); // Bloqueia o mutex
counter++; // Incrementa a variável compartilhada
pthread_mutex_unlock(&mutex); // Desbloqueia o mutex
O que vai acontecer, na prática, é que quando uma thread estiver com o mutex, e caso outra tente acessar o mesmo mutex, o sistema irá colocar esta outra thread em "wait" até que o mutex seja liberado (unlock).

Agora vamos à implementação completa do código, sincronizado com mutex:
#include <stdio.h>
#include <pthread.h>
#define NUM_THREADS 5
#define INCREMENTS 100000
int counter = 0; // Variável compartilhada entre as threads
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // Mutex para proteger o acesso
void* increment(void* arg) {
for (int i = 0; i < INCREMENTS; i++) {
pthread_mutex_lock(&mutex); // Bloqueia o mutex
counter++; // Incrementa a variável compartilhada
pthread_mutex_unlock(&mutex); // Desbloqueia o mutex
}
return NULL;
}
int main() {
pthread_t threads[NUM_THREADS];
// Cria as threads
for (int i = 0; i < NUM_THREADS; i++) {
pthread_create(&threads[i], NULL, increment, NULL);
}
// Aguarda as threads terminarem
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
printf("Valor final do counter compartilhado: %d (esperado: %d)\n", counter, NUM_THREADS * INCREMENTS);
return 0;
}
Valor final do counter compartilhado: 500000 (esperado: 500000)
Yay! Quantas palmas merece o mutex?
Last updated