Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is writing to an array of floats from 2 different threads at two different indexes safe?

float myfloats[2];

// thread A:
myFloats[0] = 1.0;

// thread B:
myFloats[1] = 1.0;

Assuming that thread A will always access index 0, thread B index 1. Is this safe or can the array get corrupted?

like image 465
Robert Avatar asked Dec 08 '13 20:12

Robert


1 Answers

The C11 n1570 draft standard appears to assert this is safe, but I assert that it is unwise.


I base my argument on the fact that the elements of an array cannot overlap in memory, and on the following clauses of the C11 draft standard.

5.1.2.4 Multi-threaded executions and data races

4. Two expression evaluations conflict if one of them modifies a memory location and the other one reads or modifies the same memory location.

25. The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.

27. NOTE 13 Compiler transformations that introduce assignments to a potentially shared memory location that would not be modified by the abstract machine are generally precluded by this standard, since such an assignment might overwrite another assignment by a different thread in cases in which an abstract machine execution would not have encountered a data race. This includes implementations of data member assignment that overwrite adjacent members in separate memory locations. We also generally preclude reordering of atomic loads in cases in which the atomics in question may alias, since this may violate the "visible sequence" rules.

28. NOTE 14 Transformations that introduce a speculative read of a potentially shared memory location may not preserve the semantics of the program as defined in this standard, since they potentially introduce a data race. However, they are typically valid in the context of an optimizing compiler that targets a specific machine with well-defined semantics for data races. They would be invalid for a hypothetical machine that is not tolerant of races or provides hardware race detection.

We learn here that it is UB for two threads to perform conflicting actions on the same memory location, but that the compiler is "generally precluded" from introducing assignments to "potentially shared" memory locations that the abstract machine would not have performed.

You assert that your threads only access (read and write) the elements at their own specific index. Since there is no question that they don't access the same memory location, it therefore appears to me that what you are doing is safe, provided you meet all other constraints, such as proper alignment of your float variables.


However, I query the wisdom of doing as you propose. Since these two memory locations are contiguous, you will likely experience a severe false sharing problem. This is because CPUs generally cache memory in "lines" of around 32 or 64 contiguous bytes, and communicate cache status using the MESI protocol.

If a thread running on one core performs a write anywhere within this cacheline, then all copies of the cacheline and everything contained in it found in other cores are invalidated, usually leading to threads on those other cores needing to reread their updated copies from main memory. This is several times slower than accesses from cache.

True sharing occurs if the threads concerned were all accessing the same part of the cacheline, because this invalidation was then justified to prevent the communicating threads from using stale data.

On the other hand, false sharing occurs if the threads were all accessing different parts of the same cacheline. In this case, invalidation was not necessary, but the hardware did it anyways because of the proximity of the accesses to each other, penalizing all of them.

like image 100
Iwillnotexist Idonotexist Avatar answered Oct 10 '22 05:10

Iwillnotexist Idonotexist