Alternativas arquiteturais quando múltiplos consumidores precisam do mesmo evento
Quando o requisito do sistema deixa de ser “distribuir trabalho” e passa a ser “notificar múltiplas tarefas sobre o mesmo fato”, o uso direto de Queue com vários consumidores se torna conceitualmente inadequado. Nesse ponto, a maturidade do projeto aparece na escolha do mecanismo correto para cada semântica, e não na tentativa de forçar a fila a se comportar como algo que ela não é.
A alternativa mais simples e eficiente para notificação múltipla é o uso de Event Groups. Um Event Group permite que várias tarefas bloqueiem esperando por um ou mais bits, e quando esses bits são setados, todas as tarefas interessadas são desbloqueadas. Esse comportamento é, por natureza, um broadcast. É ideal para eventos de estado, como “sistema inicializado”, “falha detectada” ou “conexão estabelecida”.
EventGroupHandle_t xEvents;
#define EVT_FAULT (1 << 0)
void vMonitorTask(void *pvParameters)
{
xEventGroupWaitBits(
xEvents,
EVT_FAULT,
pdTRUE,
pdFALSE,
portMAX_DELAY
);
handle_fault();
}
Aqui, várias tarefas podem esperar pelo mesmo bit e todas serão notificadas quando ele for setado. A principal limitação é que Event Groups não carregam dados, apenas sinalização. Se o evento precisa transportar contexto, outro mecanismo deve ser combinado.
Outra opção mais leve e extremamente eficiente é o uso de Task Notifications, especialmente no modo notify bits. Cada tarefa possui seu próprio registrador de notificação, o que permite um modelo explícito de fan-out: o produtor envia a mesma notificação para várias tarefas, uma por uma. Apesar de exigir mais código no produtor, esse modelo é determinístico, rápido e sem alocação dinâmica.
xTaskNotify(xTaskLog, EVT_FAULT, eSetBits);
xTaskNotify(xTaskControl, EVT_FAULT, eSetBits);
Esse padrão é muito utilizado em sistemas com requisitos rígidos de latência, pois evita listas internas complexas e overhead de filas. A desvantagem é que o produtor precisa conhecer explicitamente os consumidores, o que reduz o desacoplamento.
Quando é necessário broadcast com dados, a solução mais clara é o fan-out explícito com múltiplas filas. O produtor envia a mesma mensagem para várias Queue distintas, cada uma dedicada a um consumidor ou grupo de consumidores. Embora isso aumente o consumo de memória, o comportamento é previsível e alinhado com a semântica desejada.
xQueueSend(xQueueLog, &msg, portMAX_DELAY);
xQueueSend(xQueueControl, &msg, portMAX_DELAY);
Esse padrão é amplamente adotado em arquiteturas orientadas a eventos e pipelines de processamento bem definidos. Ele também facilita testes, pois cada consumidor pode ser validado isoladamente.
Por fim, existe um padrão híbrido bastante elegante: usar uma Queue única para distribuição de trabalho e Event Groups ou Notifications para sinalização global. Assim, a fila permanece responsável apenas por dados consumidos uma vez, enquanto os eventos notificam estados ou condições relevantes para todo o sistema. Essa separação semântica tende a produzir firmwares mais legíveis, escaláveis e fáceis de manter.
Em essência, quando múltiplos consumidores precisam reagir ao mesmo evento, o erro não está na implementação, mas na escolha da ferramenta. O FreeRTOS oferece mecanismos distintos exatamente porque cada um resolve um problema diferente.
Na próxima seção, vamos consolidar tudo isso em boas práticas e diretrizes de projeto, mostrando como decidir, de forma objetiva, quando usar Queue com múltiplos consumidores — e quando não usar.