3
votes

I am creating a threaded application that uses double buffering and I am trying to avoid a potential deadlock. The main idea is that the swap buffer thread locks out the write and the read thread. However the swap buffer thread is fast so the locks will not stay locked long. The Write and Read threads are slower but share time slices efficiently (the goal) because they lock on different mutexes. My question is there a potential deadlock with this design?

  • 3 threads...Thread A, Thread B, and Thread C.
  • 2 mutexes...Front Mutex and the Back Mutex.

  • Thread A fills the back buffer
  • Thread B swaps the buffer.
  • Thread C uses the front buffer.

  • Thread A takes theBackMutex, fills the back buffer, releases theBackMutex.
  • Thread C takes theFrontMutex, uses the front buffer, releases theFrontMutex.
  • Thread B takes theBackMutex, theFrontMutex, swaps the buffers, releases theBackMutex,release theFront Mutex

void *fill_back_buffer() {
    while(1) {
        if (0 != pthread_mutex_lock(&theBackMutex)) {
            perror("Mutex lock failed (!!):");
            exit(-1);
        }
        //should we get new data for back buffer?
        pthread_cond_wait(&theBackBufferRefresh, &theBackMutex);
        //fill back buffer
        if (0 != pthread_mutex_unlock(&theBackMutex)) {
            perror("Mutex lock failed (!!):");
            exit(-1);
        }
        //hey we done filling the back buffer!
        pthread_cond_signal(&theBackBufferFull);
    }
}


void *swap_buffers() {
    while(1) {
        if (0 != pthread_mutex_lock(&theBackMutex)) {
            perror("Mutex lock failed (!!):");
            exit(-1);
        }   
        if (0 != pthread_mutex_lock(&theFrontkMutex)) {
            perror("Mutex lock failed (!!):");
            exit(-1);
        }
        //do we have new data in the back buffer?
        pthread_cond_wait(&theBackBufferFull, &theBackMutex);

        //swap buffers
        char* tmp;
        tmp = theBufferAPtr;
        theBufferAPtr = theBufferBPtr;
        theBufferBPtr = tmp;

        if (0 != pthread_mutex_unlock(&theFrontMutex)) {
            perror("Mutex lock failed (!!):");
            exit(-1);
        } 
        if (0 != pthread_mutex_unlock(&theBackMutex)) {
            perror("Mutex lock failed (!!):");
            exit(-1); 
        }
        //hey please get more data!
        pthread_cond_signal(&theBackBufferRefresh);
        //hey you can use front buffer now!
        pthread_cond_signal(&theBufferSwapped);

    }
}   

int main(int argc, char *argv[]) {
    //initial fill of the back buffer
    pthread_cond_signal(&theBackBufferRefresh);
    while(1) {
        if (0 != pthread_mutex_lock(&theFrontMutex)) {
                perror("Mutex lock failed (!!):");
                exit(-1);
        } 
        pthread_cond_wait(&theBufferSwapped, &theFrontMutex);
        //use the front buffer and do stuff with it
        if (0 != pthread_mutex_unlock(&theFrontMutex)) {
                perror("Mutex lock failed (!!):");
                exit(-1);
        } 
    }
}
3
If you can't work it our yourself then build a Petri net model and check it that way.David Heffernan
I'll check that out, I have never used a petri net model before. Thanks.eat_a_lemon
Once you've built your model you can import it into a tool and get the tool to find your deadlocks. Pretty neat stuff!David Heffernan
@David -- OR you can just look at the code and see that the deadlock is obvious.Heath Hunnicutt
@Heath That's the "If you can't work it our yourself" part of my comment. Petri nets are also a pretty good learning tool for threading novices. I know that they helped me way back when!David Heffernan

3 Answers

2
votes

Condition variables are supposed to be used to signal a change in the state of some (mutex-protected) shared data. You can't use them on their own. Consider what would happen if a thread signals a condition before there is another thread waiting for that condition.

1
votes

I don't see where you create any threads. I'll assume you create the threads.

swap_buffers() and fill_back_buffer() do contain the classic deadlock implementation. When swap_buffers() is waiting on theBackBufferFull, it has locked theBackMutex. Meanwhile, fill_back_buffer() is waiting on theBackMutex before it sets the signal theBackBufferFull. Therefore, theBackBufferFull will never be signaled, because theBackMutex cannot be released. This is the classic deadlock condition.

0
votes

Try to do it without using additional thread for swapping.