...
Another approach is to embed a concurrently accessed variable inside a union, along with a long
variable, or at least some padding to ensure that the concurrent variable is the only element to be accessed at that address. This technique would effectively guarantee that no other variables are accessed or modified when the concurrent variable is accessed or modified.
Noncompliant Code Example (
...
C99)
In this noncompliant code example, two executing threads simultaneously access modify two separate distinct members of a global struct.
Code Block | ||||
---|---|---|---|---|
| ||||
struct multi_threaded_flags { unsigned intchar flag1 : 2; unsigned intchar flag2 : 2; }; struct multi_threaded_flags flags; void thread1(void) { flags.flag1 = 1; } void thread2(void) { flags.flag2 = 2; } |
Although this code appears to be harmless, it is likely possible that flag1
and flag2
are stored in the same byteword. If both assignments occur on a thread-scheduling interleaving that ends with both stores occurring after one another, it is possible that only one of the flags will be set as intended, and the other flag will equal its previous value, because both bit-fields chars are represented by the same byteword, which is the smallest unit the processor can work on. C99 makes no guarantees that these flags can be modified concurrently.
Even though each thread is modifying a separate object, they may be modifying the same word in memory. A similar problem is discussed in CON00-C. Avoid race conditions with multiple threads, but this example can be harder to diagnose because it is not immediately obvious that the same memory location is being modified.
Compliant Solution (C11)
The same code is guarenteed to be safe on a C11-compliant platform. Unlike C99, C11 explicitly defines a memory location, and provides the following note, in chapter 3, section 14.2:
NOTE 1 Two threads of execution can update and access separate memory locations without interfering with each other.
Noncompliant Code Example (Bit-field)
Adjacent bit-fields may be stored in a single memory location, and therefore modifying adjacent bit-fields in different threads is undefined behavior, even in C11.
Code Block | ||||
---|---|---|---|---|
| ||||
struct multi_threaded_flags {
unsigned int flag1 : 2;
unsigned int flag2 : 2;
};
struct multi_threaded_flags flags;
void thread1(void) {
flags.flag1 = 1;
}
void thread2(void) {
flags.flag2 = 2;
}
|
C11, chapter 3, section 14.3 states:
NOTE 2 A bit-field and an adjacent non-bit-field member are in separate memory locations. The same applies to two bit-fields, if one is declared inside a nested structure declaration and the other is not, or if the two are separated by a zero-length bit-field declaration, or if they are separated by a non-bit-field member declaration. It is not safe to concurrently update two non-atomic bit-fields in the same structure if all members declared between them are also (non-zero-length) bit-fields, no matter what the sizes of those intervening bit-fields happen to be.
For example, the following sequence of events can occur:
Code Block |
---|
Thread 1: register 0 = flags
Thread 1: register 0 &= ~mask(flag1)
Thread 2: register 0 = flags
Thread 2: register 0 &= ~mask(flag2)
Thread 1: register 0 |= 1 << shift(flag1)
Thread 1: flags = register 0
Thread 2: register 0 |= 2 << shift(flag2)
Thread 2: flags = register 0
|
...
Compliant Solution (Bit-field)
This compliant solution protects all accesses of the flags with a mutex, thereby preventing any data races. Finally, the flags are embedded in a union alongside a long
, and a static assertion guarantees that the flags do not occupy more space than the long
. This technique prevents any data not checked by the mutex from being accessed or modified with the bit-fields on platforms that do not comply with C11.
Code Block | ||||
---|---|---|---|---|
| ||||
struct multi_threaded_flags { unsigned int flag1 : 2; unsigned int flag2 : 2; }; union mtf_protect { struct multi_threaded_flags s; long padding; }; static_assert(sizeof(long) >= sizeof(struct multi_threaded_flags)); struct mtf_mutex { union mtf_protect u; mtx_t mutex; }; struct mtf_mutex flags; void thread1(void) { int result; if ((result = mtx_lock(&flags.mutex)) == thrd_error) { /* Handle error */ } flags.u.s.flag1 = 1; if ((result = mtx_unlock(&flags.mutex)) == thrd_error) { /* Handle error */ } } void thread2(void) { int result; if ((result = mtx_lock(&flags.mutex)) == thrd_error) { /* Handle error */ } flags.u.s.flag2 = 2; if ((result = mtx_unlock(&flags.mutex)) == thrd_error) { /* Handle error */ } } |
...
ISO/IEC 9899:2011 Section 6.7.2.1, "Structure and union specifiers", Section 3.14, "Memory Location"
...