Table of Contents
- Introdução: filas no FreeRTOS e o cenário de múltiplos consumidores
- Modelos clássicos de múltiplos consumidores e o impacto no escalonador
- Cuidados essenciais e armadilhas comuns ao usar Queue com vários consumidores
- Alternativas arquiteturais quando múltiplos consumidores precisam do mesmo evento
- Boas práticas, diretrizes de projeto e fechamento
Introdução: filas no FreeRTOS e o cenário de múltiplos consumidores
No FreeRTOS, Queue é um dos mecanismos centrais de comunicação e sincronização entre tarefas. Diferente de simples buffers, uma fila carrega não apenas dados, mas também semântica de sincronização, permitindo que tarefas produtoras e consumidoras operem de forma desacoplada no tempo. Em sistemas reais — especialmente em firmware para sensores, comunicação, aquisição de dados e pipelines de processamento — é muito comum termos um produtor e vários consumidores, todos interessados nos dados que chegam pela mesma fila.
O primeiro ponto importante, e frequentemente negligenciado, é que uma Queue do FreeRTOS não é um mecanismo de broadcast. Ela segue estritamente o modelo FIFO (First-In, First-Out) com remoção destrutiva do item. Isso significa que, quando múltiplas tarefas consumidoras bloqueiam na mesma fila, apenas uma delas receberá cada item enviado, e essa escolha é feita pelo escalonador com base em prioridade e estado das tarefas. Esse detalhe muda completamente a forma como o sistema deve ser arquitetado.
Esse comportamento é ideal quando os consumidores representam workers concorrentes, ou seja, tarefas equivalentes que disputam trabalho. Um exemplo clássico é um sistema em que uma interrupção de ADC empilha amostras em uma fila, e várias tarefas de processamento estatístico competem para consumir esses dados. Nesse caso, a fila atua como um work queue, balanceando carga automaticamente entre os consumidores.
Por outro lado, quando o objetivo é que todos os consumidores vejam todos os eventos, a Queue isoladamente é a escolha errada. Muitos bugs em sistemas embarcados com FreeRTOS surgem exatamente desse equívoco conceitual: o desenvolvedor assume que várias tarefas “escutando” a mesma fila receberão as mesmas mensagens, o que simplesmente não acontece. Esse erro se manifesta como perda intermitente de eventos, estados inconsistentes e falhas difíceis de reproduzir.
Para ilustrar o cenário correto de múltiplos consumidores concorrentes, considere o exemplo abaixo, onde várias tarefas processam mensagens vindas de um único produtor:
typedef struct {
uint32_t id;
uint16_t value;
} message_t;
QueueHandle_t xQueue;
void vProducerTask(void *pvParameters)
{
message_t msg;
uint32_t counter = 0;
for (;;)
{
msg.id = counter++;
msg.value = counter * 10;
xQueueSend(xQueue, &msg, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void vConsumerTask(void *pvParameters)
{
message_t msg;
for (;;)
{
if (xQueueReceive(xQueue, &msg, portMAX_DELAY) == pdPASS)
{
/* Cada mensagem será processada por APENAS um consumidor */
process_message(&msg);
}
}
}
Nesse modelo, várias instâncias de vConsumerTask podem ser criadas, e cada mensagem enviada pelo produtor será entregue a apenas uma delas. O FreeRTOS garante exclusão mútua implícita no acesso à fila, evitando a necessidade de mutex para esse fluxo específico.
Nesta artigo, vamos aprofundar quando esse modelo é ideal, quais implicações surgem em termos de escalonamento, prioridade e starvation, como evitar armadilhas comuns, e quais alternativas arquiteturais devem ser usadas quando o requisito é difusão (fan-out) e não competição.