2
votes

I'm reading stevens's book: apue. 2e. I have encountered a problem, about Mutex.

Let's see a peice of code first: (the following code comes from figure 11.10 of apue 2e)

#include <stdlib.h>
#include <pthread.h>

struct foo {
    int f_count;
    pthread_mutex_t f_lock;
    /* ... more stuff here ... */
};

struct foo *
foo_alloc(void) /* allocate the object */
{
    struct foo *fp;

    if((fp = malloc(sizeof(struct foo))) != NULL){
        fp->f_count = 1;
        if(pthread_mutex_init(&fp->f_lock, NULL) != 0){
            free(fp);
            return NULL;
        }
        /* ... continue initialization ... */
    }
    return (fp);
}

void
foo_hold(struct foo *fp) /* add a reference to the object */
{
    pthread_mutex_lock(&fp->f_lock);
    fp->f_count++;
    pthread_mutex_unlock(&fp->f_lock);
}

void
foo_rele(struct foo *fp) /* release a reference to the object */
{
    pthread_mutex_lock(&fp->f_lock);
    if(--fp->f_count == 0){ /* last reference */
        pthread_mutex_unlock(&fp->f_lock); /* step 1 */
        pthread_mutex_destroy(&fp->f_lock); /* step 2 */
        free(fp);
    }else{
        pthread_mutex_unlock(&fp->f_lock);
    }
}

Assuming we have two threads: thread1 and thread2. thread1 is running right now.

it calls function foo_rele and has finished the execution of step 1(see above), and prepare to execute step2.

however, context switch occurs at this moment. and thread2 starts to execute.

thread2 locks the lock fp->f_lock then does something. but before thread2 unlocks the lock, context switch occurs again.

thread2 stops executing and thread1 starts to execute. thread1 destroy the lock and it's known to us all that error generates.

so, my problem is: Is the situation mentioned above possible to happen? how can we avoid it and is there any interface(API) that can unlock and destroy a mutex atomically?

2

2 Answers

1
votes

This is not well-designed because thread2 accesses an object which might have been destroyed already. Add a reference (increment f_count) before starting thread2. That way thread2 already starts out with the guarantee that the object is stable while it is running.

1
votes

I agree with usr (+1), the design is wrong.

Generally speaking, before one thread can destroy anything it must establish that all other threads have finished using it. That requires some other form of inter-thread synchronisation. What you need is a way in which thread 1 can tell thread 2 that the resource needs to be freed, and for thread 2 to acknowledge that so that thread 1 can call foo_rele() knowing for sure that thread 2 will not try to use fp ever again. Or, for thread 2 telling thread 1 that it no longer needs fp and thread 1 calling foo_rele() as a consequence.

This can be accomplished several ways. One is for thread 1 to set a flag under the protection of the lock, and then wait for thread 2 to quit. Meanwhile, thread 2 eventually gets round to seeing the flag, and then quits. This releases thread 1 which then calls foo_rele(). Another way is message passing over pipes (my preferred means of inter-thread synchronisation); something along the lines of thread 1 -> thread 2, "Please stop using fp": thread 2 -> thread 1, "Ok" (though of course a better defined message set based on enums is advisable, but you get my meaning).

What you can't ever have is thread 1 calling foo_rele() without thread 2 either knowing that it must never touch fp ever again or thread 2 having already quit.