Search code examples
cmultithreadinggccthread-safetyvolatile

Gcc optimizes condition


I have the following code:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

char condA = 0;
volatile int vA = 0;
volatile int vB = 0;
void* thread_a(void* arg) {
    while (1) {
        if (1 == condA) {
            vA += 1;
            usleep(100);
        } else {
            ;
//          vB += 1;
//          usleep(100);
        }
    }
    return NULL;
}

void* thread_b(void* arg) {
    condA = 1;
    return NULL;
}

int main(void) {

    pthread_t threadA;
    pthread_t threadB;

    if (0 != pthread_create(&threadA, NULL, thread_a, NULL)) {
        return -2;
    }
    sleep(1);

    if (0 != pthread_create(&threadB, NULL, thread_b, NULL)) {
        return -1;
    }
    pthread_join(threadB, NULL);
    sleep(1);
    pthread_cancel(threadA);

    printf("value A is %d\n", vA);
    printf("value B is %d\n", vB);
    return 0;
}

I run my gcc 5.4.0 with either -O0 and -O1.

On O0, the output is: value A is 501

On O1, the output is: value A is 0

I know that setting condA to volatile will fix this, but why is it so? Why does the compiler optimize the condition if it is obviously set in another thread?

Also, if I uncomment the code in the else-statement, it works as expected.

A partial answer I found: https://gcc.gnu.org/onlinedocs/gcc-5.4.0/gcc/Volatiles.html#Volatiles "C has the concept of volatile objects. These are normally accessed by pointers and used for accessing hardware or inter-thread communication.[...]"

But this does not explain the fact, that if I uncomment the code in the else statement the program will work with -O1.


Solution

  • the variable which has to be volatile, is not in your code

    char condA = 0;
    

    so the thread never reads it form the memory to check if the condition is met

    .L3:
      cmp al, 1
      jne .L3
    

    if you add the volatile keyword the code changes to the correct one

    .L8:
            movzx   eax, BYTE PTR condA[rip]
            cmp     al, 1
            jne     .L8
    

    gcc handles volatile correct. But volatile does not guarantee anything else especially no atomicity and coherency.

    https://godbolt.org/z/dDTUz-


    EDIT

    The answer for the second question 2 is quite simple.

    The compiler does not see ant way this flag can be changed when the program is executed (as there is not direct call to the thread function). No functions are called in the thread as well (as they can be side effects prone). So the compiler optimizes out the reads from the storage as not needed and keeps the value in the register(s).