liteyear wrote on Monday, November 07, 2016:
In any multithreaded design I find myself often implementing this sort of pattern:
void my_task()
{
while(1)
{
wait_on_queue(requestQ, &theOperation);
raise_flag(busy);
process_queue_item(theOperation);
lower_flag(busy);
}
}
void request_operation(newOperation)
{
add_to_queue(requestQ, newOperation);
}
This works pretty well when only one other task is requesting operations and waiting on the results. When you have multiple tasks doing the same however, you quickly run into edge cases where for example the task is between wait_on_queue()
and raise_flag()
or lower_flag()
and wait_on_queue()
. In both those cases, it is difficult to determine whether it is safe to assume the task is idle.
For example, imagine the implementation of a “wait_for_operation_to_complete()
” function in another task. If you call request_operation()
and then wait on len(queue) == 0 && !busy
, you could return early because the condition is true in between wait_on_queue()
and raise_flag()
.
In the pthreads world I’ve successfully modified this pattern to only pop the operation off the queue after processing it. That way len(queue) == 0
is a good indication of idleness. If I were to replicate this in FreeRTOS it might look like this:
QueueHandle_t gOperationQueue = NULL;
void my_task(void *pvParameters)
{
gOperationQueue = xQueueCreate(3, sizeof(sOperation));
while(1)
{
xQueuePeek(gOperationQueue, &theOp, portMAX_DELAY);
process_operation(theOp);
xQueueReceive(gOperationQueue, &theOp, portMAX_DELAY);
}
}
void request_operation(newOp)
{
while(!gOperationQueue)
vTaskDelay(5 / portTICK_PERIOD_MS);
xQueueSend(gOperationQueue, newOp, 10 / portTICK_PERIOD_MS);
}
bool task_is_idle(void)
{
return uxQueueMessagesWaiting(gOperationQueue) == 0;
}
This is neat because it removes the busy semaphore (although you’d still need to add a mutex if you want concurrent access to variables that the task can update), but I wonder if this is best practice, given that the xQueuePeek and xQueueReceive functions don’t seem to hint at this sort of usage (eg. the &theOp
argument is required for both, even though it is useless in the xQueueReceive
call).
How do others tend to implement this sort of pattern? It seems like such a common paradigm with not a lot written about it (that I’ve seen).