- Nayla Sahra Santos das Chagas - 202000024525
- Túlio Sousa de Gois - 202000024599
A nossa solução implementa um sistema distribuído de relógios vetoriais usando MPI para comunicação entre processos e pthreads para concorrência dentro de cada processo. O sistema segue um modelo produtor-consumidor com buffers intermediários.
-
Relógio Vetorial
O relógio vetorial é implementado como uma estruturaVectorClock
que contém um array de inteiros. O tamanho deste array é igual ao número de processos no sistema (definido porTHREAD_NUM
).typedef struct { int clock[THREAD_NUM]; } VectorClock;
-
Buffer
Dois buffers compartilhados são utilizados:buffer_entrada
ebuffer_saida
. Ambos são arrays deVectorClock
com tamanho fixoBUFFER_SIZE
.VectorClock buffer_entrada[BUFFER_SIZE]; VectorClock buffer_saida[BUFFER_SIZE];
-
Sincronização
A sincronização é implementada usando mutexes e variáveis de condição para acessar os buffers, evitando condições de corrida:pthread_mutex_t mutex_entrada, mutex_saida; pthread_cond_t can_produce_entrada, can_consume_entrada; pthread_cond_t can_produce_saida, can_consume_saida;
-
Thread de Entrada
- Recebe mensagens via MPI
- Atualiza o relógio vetorial local
- Coloca o relógio atualizado no buffer de entrada
-
Thread de Relógios
- Consome relógios do buffer de entrada
- Atualiza o relógio local (evento local)
- Coloca o relógio atualizado no buffer de saída
-
Thread de Saída
- Consome relógios do buffer de saída
- Envia o relógio para o próximo processo via MPI
-
Inicialização
- O programa inicia
THREAD_NUM
processos MPI - Cada processo cria 3 threads: entrada, relógios e saída
- O processo 0 envia uma mensagem inicial para iniciar o fluxo
- O programa inicia
-
Comunicação
- Os processos formam um anel lógico
- Cada processo envia mensagens para o próximo e recebe do anterior
-
Atualização do Relógio
- Na recepção de uma mensagem, o relógio é atualizado com o máximo entre o local e o recebido
- Um evento local incrementa apenas o contador do processo atual
Os cenários de teste estão implementados nas funções de cada thread. O cenário principal é o de buffer cheio/vazio, que é tratado usando as variáveis de condição.
-
Buffer de Entrada Cheio
while (buffer_entrada_count == BUFFER_SIZE) { printf("Processo %d: Thread Entrada - Fila cheia, aguardando...\n", rank); pthread_cond_wait(&can_produce_entrada, &mutex_entrada); }
-
Buffer de Saída Vazio
while (buffer_saida_count == 0) { printf("Processo %d: Thread Saída - Fila de saída vazia, aguardando...\n", rank); pthread_cond_wait(&can_consume_saida, &mutex_saida); }
- Sincronização das threads;
- Validação do resultado dos relógios vetoriais;