Here is an example I just cooked up, and might show better what I mean.
/* FreeRTOS.org includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdatomic.h>
/* Demo includes. */
#include "basic_io.h"
#include "leds.h"
#include <stdio.h>
/* USE_STDATOMIC default is 0 */
#define USE_STDATOMIC 0
/* MAKE_CNT_ATOMIC default is 0 */
#define MAKE_CNT_ATOMIC 0
/* USE_STDATOMIC 0, MAKE_CNT_ATOMIC 0
* cnt is a non atomic counter
* acnt is protected by taskENTER_CRITICAL/taskEXIT_CRITICAL
*
* Safe1 Task2 1000
* Unsafe Task4 1466
* Unsafe Task6 2466
* Unsafe Task7 3466
* Unsafe Task3 1000
* Safe1 Task5 2000
* Task1 cnt 1000 - wrong - order of execution problem
* Task1 acnt 2000 - correct
*/
/* USE_STDATOMIC 0, MAKE_CNT_ATOMIC 1
* cnt is an atomic data type
* acnt is protected by taskENTER_CRITICAL/taskEXIT_CRITICAL
*
* Safe1 Task2 1000
* Unsafe Task4 1295
* Unsafe Task6 2295
* Unsafe Task7 3295
* Unsafe Task3 4000
* Safe1 Task5 2000
* Task1 cnt 4000 - correct
* Task1 acnt 2000 - correct
*
*/
/* USE_STDATOMIC 1, MAKE_CNT_ATOMIC 1
* cnt is an atomic data type
* acnt is an atomic data type
*
* Safe1 Task2 1000
* Unsafe Task3 1000
* Safe1 Task5 2000
* Unsafe Task6 3000
* Unsafe Task7 4000
* Unsafe Task4 4000
* Task1 cnt 4000 - correct
* Task1 acnt 2000 - correct
*/
#if USE_STDATOMIC > 0
atomic_int acnt;
#else
/* Manual atomic implementation using critical sections */
volatile uint32_t acnt = 0;
#endif
#if MAKE_CNT_ATOMIC > 0
atomic_int cnt;
#else
/* The non-atomic counter (prone to race conditions) */
volatile uint32_t cnt = 0;
#endif
void vTaskFunctionUnsafe(void* pvParameters)
{
char *pcTaskName;
pcTaskName = ( char * ) pvParameters;
for (int n = 0; n < 1000; ++n)
{
/* Non-atomic increment (race condition possible) */
#if MAKE_CNT_ATOMIC > 0
/* Non-atomic increment (race condition possible) */
/* but should work if defined as atomic */
++cnt;
//atomic_fetch_add(&acnt, 1);
// relaxed memory order equivalent:
// atomic_fetch_add_explicit(&acnt, 1, memory_order_relaxed);
#else
/* Non-atomic increment (race condition possible) */
++cnt;
#endif
}
vPrintString( "Unsafe " );
vPrintStringAndNumber( pcTaskName, cnt );
/* In FreeRTOS, tasks usually run indefinitely or delete themselves */
vTaskDelete(NULL);
}
void vTaskFunctionSafe1(void* pvParameters)
{
char *pcTaskName;
pcTaskName = ( char * ) pvParameters;
for (int n = 0; n < 1000; ++n)
{
/* Atomic increment using stdatomic.h or critical sections */
#if USE_STDATOMIC > 0
/* acnt is an atomic data type */
++acnt;
//atomic_fetch_add(&acnt, 1);
// relaxed memory order equivalent:
// atomic_fetch_add_explicit(&acnt, 1, memory_order_relaxed);
#else
/* Use a critical section for manual atomicity */
taskENTER_CRITICAL();
++acnt;
taskEXIT_CRITICAL();
#endif
}
vPrintString( "Safe1 " );
printf("%s %u\n", pcTaskName, acnt);
/* In FreeRTOS, tasks usually run indefinitely or delete themselves */
vTaskDelete(NULL);
}
void vTaskFunctionPrinter(void* pvParameters)
{
char *pcTaskName;
pcTaskName = ( char * ) pvParameters;
/* I know it's ugly just to wait, but that's for simplicity */
vTaskDelay(pdMS_TO_TICKS(1000));
printf("%s cnt %u\n", pcTaskName, cnt);
printf("%s acnt %u\n", pcTaskName, acnt);
vTaskDelete(NULL);
}
void vStartAtomicTest( void ) {
xTaskCreate( vTaskFunctionPrinter, "Task1", 240, (void*)"Task1", tskIDLE_PRIORITY + 1, NULL );
xTaskCreate( vTaskFunctionSafe1, "Task2", 240, (void*)"Task2", tskIDLE_PRIORITY + 1, NULL );
xTaskCreate( vTaskFunctionUnsafe, "Task3", 240, (void*)"Task3", tskIDLE_PRIORITY + 1, NULL );
xTaskCreate( vTaskFunctionUnsafe, "Task4", 240, (void*)"Task4", tskIDLE_PRIORITY + 1, NULL );
xTaskCreate( vTaskFunctionSafe1, "Task5", 240, (void*)"Task5", tskIDLE_PRIORITY + 1, NULL );
xTaskCreate( vTaskFunctionUnsafe, "Task6", 240, (void*)"Task6", tskIDLE_PRIORITY + 1, NULL );
xTaskCreate( vTaskFunctionUnsafe, "Task7", 240, (void*)"Task7", tskIDLE_PRIORITY + 1, NULL );
}
int main( void ) {
/* Init the semi-hosting. */
printf( "\n" );
vStartAtomicTest();
/* Start the scheduler so our tasks start executing. */
vTaskStartScheduler();
/* If all is well we will never reach here as the scheduler will now be
running. If we do reach here then it is likely that there was insufficient
heap available for the idle task to be created. */
for( ;; );
return 0;
}
/*-----------------------------------------------------------*/
void vApplicationMallocFailedHook( void )
{
/* This function will only be called if an API call to create a task, queue
or semaphore fails because there is too little heap RAM remaining. */
for( ;; );
}
/*-----------------------------------------------------------*/
void vApplicationStackOverflowHook( TaskHandle_t xTask, char *pcTaskName )
{
/* This function will only be called if a task overflows its stack. Note
that stack overflow checking does slow down the context switch
implementation. */
for( ;; );
}
/*-----------------------------------------------------------*/
void vApplicationIdleHook( void )
{
#if PRINTOUTS == 1
/* This example does not use the idle hook to perform any processing. */
printf("Idle Hook\n");
#endif
#if LEDS == 1
led_blue_invert();
#endif
}
/*-----------------------------------------------------------*/
void vApplicationTickHook( void )
{
/* This example does not use the tick hook to perform any processing. */
/* printf("Tick Hook\n"); */
}