3
votes

I have two threads, using C pthreads on linux. One of them writes data and the other is reading it. I'm using a variable to allow the read thread when is allowed to read and the write one when is allowed. So the mutex applies to this boolean variable called "newData". My question is: do I need to lock/unlock the mutex around the accesses inside the "if" condition? Both ways work, but I think just because the chances of overlapping a write/read over a this variable are very few. I show both alternatives to explain better my question:

Thread 1:

pthread_mutex_lock( &lattice_mutex );
if (!newData) {
    pthread_mutex_unlock( &lattice_mutex );
    uchar *lattice_pos = lattice;
    int i;
    for(i=0; i<size; i++) {
        *lattice_pos = rand()%CHAR_MAX;
        lattice_pos++;
    }
    pthread_mutex_lock( &lattice_mutex );
    newData = TRUE;
    pthread_mutex_unlock( &lattice_mutex );
} else {
    pthread_mutex_unlock( &lattice_mutex );
}

Thread 2:

pthread_mutex_lock( &lattice_mutex );
if(newData) {
    pthread_mutex_unlock( &lattice_mutex );
    renderUpdate();
    pthread_mutex_lock( &lattice_mutex );
    newData = FALSE;
    pthread_mutex_unlock( &lattice_mutex );
} else {
    pthread_mutex_unlock( &lattice_mutex );
}

Second version, which works but I don't know if it is correct:

Thread 1:

if (!newData) {
    uchar *lattice_pos = lattice;
    int i;
    for(i=0; i<size; i++) {
        *lattice_pos = rand()%CHAR_MAX;
        lattice_pos++;
    }
    pthread_mutex_lock( &lattice_mutex );
    newData = TRUE;
    pthread_mutex_unlock( &lattice_mutex );
}

Thread 2:

if(newData) {
    renderUpdate();
    pthread_mutex_lock( &lattice_mutex );
    newData = FALSE;
    pthread_mutex_unlock( &lattice_mutex );
}
2
Simply put, the standard says that you need a mutex if you want to access a variable while another thread is or might be modifying it. You might have platform-specific features (such as atomic operations) that allow you to avoid this, but using mutexes around all accesses is the way that is guaranteed to work on all platforms.David Schwartz

2 Answers

4
votes

This is derived from your first version - it is somewhat simpler.

Thread 1: writer

pthread_mutex_lock(&lattice_mutex);
if (!newData) {
    pthread_mutex_unlock(&lattice_mutex);  // Omit?
    uchar *lattice_pos = lattice;
    int i;
    for (i = 0; i < size; i++)
        *lattice_pos++ = rand() % CHAR_MAX;
    pthread_mutex_lock(&lattice_mutex);   // Omit?
    newData = TRUE;
}
pthread_mutex_unlock(&lattice_mutex);

Thread 2: reader

pthread_mutex_lock(&lattice_mutex);
if (newData) {
    pthread_mutex_unlock(&lattice_mutex);   // Omit?
    renderUpdate();
    pthread_mutex_lock(&lattice_mutex);     // Omit?
    newData = FALSE;
}
pthread_mutex_unlock(&lattice_mutex);

It depends on exactly how the lattice information is meant to be used, but given the name of the mutex, I think you should keep it locked while you are modifying the lattice, hence the two pairs of lines marked 'Omit?' should be removed. Otherwise, the lattice is not protected from concurrent access.

Added: I think the second version is erroneous - it doesn't properly protect the lattice.

3
votes

The first version is correct, you need the mutex on both writes and reads.

However, AFAIK, almost all architectures out there have simple read and write accesses to one unit of data (eg. int) atomic. However, note that on architectures with weak memory ordering, you might have issues like seeing the "buffer-full" flag to true before the buffer actually contains the data.

Note that the code is probably not the best you can do, because it never sleeps (uses busy waiting). If you wanted to wait for the data in either thread, you would have to use a condition variable with the mutex.