Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C - Can global pointers be modified by different threads?

Do global pointers have a scope that exist between threads?

For instance, suppose I have two files, file1.c and file2.c:

file1.c:

uint64_t *g_ptr = NULL;

modify_ptr(&g_ptr) { 
    //code to modify g_ptr to point to a valid address 
}

read_from_addr() {
    //code which uses g_ptr to read values from the memory it's pointing to
}

file2.c:

function2A() {
    read_from_addr();
}

So I have threadA which runs through file1.c and executes modify_ptr(&g_ptr) and also read_from_addr(). And then threadB runs, and it runs through file2.c executing function2A().

My question is: Does threadB see that g_ptr is modified? Or does it still see that it's pointing to NULL?

If that's not the case, what does it mean for a pointer to be global? And how do I ensure that this pointer is accessible between different threads?

Please let me know if I need to clarify anything. Thanks

like image 884
OfLettersAndNumbers Avatar asked Sep 03 '13 18:09

OfLettersAndNumbers


1 Answers

My question is: Does threadB see that g_ptr is modified? Or does it still see that it's pointing to NULL?

Maybe. If accessed without any sort of external synchronization, you're likely to see bizarre, highly non-reproducible results -- in certain cases, the compiler may make certain optimizations based on its analysis of your code which can stem from assuming that a variable is not modified during certain code paths. For example, consider this code:

// Global variable
int global = 0;

// Thread 1 runs this code:
while (global == 0)
{
    // Do nothing
}

// Thread 2 at some point does this:
global = 1;

In this case, the compiler can see that global is not modified inside the while loop, and it doesn't call any external functions, so it can "optimize" it into something like this:

if (global == 0)
{
    while (1)
    {
        // Do nothing
    }
}

Adding the volatile keyword to the declaration of the variable prevents the compiler from making this optimization, but this was not the intended use case of volatile when the C language was standardized. Adding volatile here will only slow down your program in small ways and mask the real problem -- lack of proper synchronization.

The proper way to manage global variables that need to be accessed simultaneously from multiple threads is to use mutexes to protect them1. For example, here's a simple implementation of modify_ptr using a POSIX threads mutex:

uint64_t *g_ptr = NULL;
pthread_mutex_t g_ptr_mutex = PTHREAD_MUTEX_INITIALIZER;

void modify_ptr(uint64_t **ptr, pthread_mutex_t *mutex)
{
    // Lock the mutex, assign the pointer to a new value, then unlock the mutex
    pthread_mutex_lock(mutex);
    *ptr = ...;
    pthread_mutex_unlock(mutex);
}

void read_from_addr()
{
    modify_ptr(&g_ptr, &g_ptr_mutex);
}

Mutex functions ensure that the proper memory barriers are inserted, so any changes made to a variable protected by a mutex will be properly propagated to other CPU cores, provided that every access of the variable (including reads!) is protected by the mutex.

1) You can also use specialized lock-free data structures, but those are an advanced technique and are very easy to get wrong

like image 75
Adam Rosenfield Avatar answered Nov 02 '22 23:11

Adam Rosenfield