In any multithreaded design I find myself often implementing this sort of pattern:
~~~
void my
task()
{
while(1)
{
waiton
queue(requestQ, &theOperation);
raiseflag(busy);
process_queue_item(theOperation);
lower_flag(busy);
}
}
void request
operation(newOperation)
{
addto_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 / portTICKPERIOD_MS);
xQueueSend(gOperationQueue, newOp, 10 / portTICK_PERIOD_MS);
}
bool task
isidle(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).