Is accessing different elements of an array thread-safe?

char my_array[10];

Let’s say taskA changes the value of my_array[0] and taskB changes the value of my_array[1] , is this thread safe?

My instinct tells me that it is not thread-safe, because since arrays cannot have padding bytes between elements, and usually CPU access (read/write) multiple bytes at a time, that means changing the value of my_array[0] would result in writing adjacent array elements which may have changed in the meantime.

More specifically, is this guaranteed to be thread-safe with single core MCUs without cache?

And if this is thread-safe, is this guaranteed by the CPU or by FreeRTOS?

1 Like

It depends on if the compiler will access each byte atomically, or if it needs to read then write a full “word” to write a new value to just one byte.

Most processor have individual byte write instructions, so at the single core level, you should be save. Even a cache won’t get in the way for a single core, as all threads are on the same core, so use the same cache. If the processor needs to read a bigger chunk to update, it does it “under the hood” and the program doesn’t see the problem.

If the processor doesn’t have byte writes (or the compiler doesn’t use them) but does a word read as an instruction, changes a piece, then write the word, you will have problems.

Where you get problems is with multi-core, as core will tend to share memory at the cache-line level, so that would not be safe if the two tasks might be on different cores.

2 Likes

What about members of a packed struct?

__attribute__((packed))
typedef struct {
    char c; // 1 byte
    int i;  // 4 bytes
} my_struct_t:

my_struct_t arr[2];

int *a = &(arr[0].i);
int *b = &(arr[1].i);

In this case the i members are not aligned in memory, the following could be an in-memory representation of arr

  --- arr[0].c        --- arr[1].c 
 |                   | 
\ /  arr[0].i       \ /  arr[1].i
|-| |-------------| |-| |-------------|
 _   _   _   _   _   _   _   _   _   _
 0   1   2   3   4   5   6   7   8   9

a points to address 0x01 and b to address 0x06, in this case the processor to access *a should access the memory blocks [0x00 - 0x03] and [0x04 - 0x07], ending up accessing arr[0].c, arr[1].c and arr[1].i.

So if in this case we had taskA writing to *a and taskB writing to *b, would that be thread-safe?

For a “packed” struct, it depends on how the complier generates the code (particularly the writes), If it uses byte writes for the 4 bytes of the packed int, then no problem, which is the most likely case. If it makes two word size writes, and loads the old data into the writes, it will have problems.

The key is that for misaligned accesses, since byte access IS available on most processors, accessing in 4 separate reads and writes the data at +1, +2, +3, and +4 (and thus not touching the others) is the simplest. This get arround the problem that to use word only accesses, it would need different processing for b then a, as they are at different offsets within the word.

For word access based machines, to “fake” byte accessing, this was done, but most modern machines have byte accessing instructions, where the processor handles the issues, and may even recognize the multiple writes when done “right” and merge them together.

1 Like

If you are sure that different tasks will always access different indexes of the array, why can you not define them separately. Something like the following:

my_struct_t core0Def;
my_struct_t core1Def;