C11 introduced support for multiple threads of execution, which affords the possibility of data races. A program contains a data race if an object in it is accessed1 by two different threads, where at least one of the accesses is non-atomic, at least one modifies the object, and program semantics fail to ensure that the two accesses cannot overlap temporally.2 Note well that actual concurrency of the accesses involved is not a condition for a data race; data races cover a broader class of issues arising from (allowed) inconsistencies in different threads' views of memory.
Consider this example:
#include <threads.h>
int a = 0;
int Function( void* ignore )
{
a = 1;
return 0;
}
int main( void )
{
thrd_t id;
thrd_create( &id , Function , NULL );
int b = a;
thrd_join( id , NULL );
}
The main thread calls thrd_create
to start a new thread running function Function
. The second thread modifies a
, and the main thread reads a
. Neither of those access is atomic, and the two threads do nothing either individually or jointly to ensure that they do not overlap, so there is a data race.
Among the ways this program could avoid the data race are
a
before starting the other thread;a
after ensuring via thrd_join
that the other has terminated;a
and unlocking it afterward.As the mutex option demonstrates, avoiding a data race does not require ensuring a specific order of operations, such as the child thread modifying a
before the main thread reads it; it is sufficient (for avoiding a data race) to ensure that for a given execution, one access will happen before the other.
1 Modifying or reading an object.
2 (Quoted from ISO:IEC 9889:201x, section 5.1.2.4 "Multi-threaded executions and data races")
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.