cong cong - 1 month ago 8
Linux Question

How to safely and correctly destroy a mutex in Linux using pthread_mutex_destroy?

I read about APUE 3rd, 11.6.1 Mutexes, there is an example about lock and unlock a mutex in this chapter:

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

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

if ((fp = malloc(sizeof(struct foo))) != NULL) {
fp->f_count = 1;
fp->f_id = id;
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);
pthread_mutex_destroy(&fp->f_lock);
free(fp);
} else {
pthread_mutex_unlock(&fp->f_lock);
}
}


In foo_rele, there is an race condition between pthread_mutex_unlock and pthread_mutex_destroy, so, B thread can call pthread_mutex_lock between pthread_mutex_unlock and pthread_mutex_destroy in A thread which will cause an undefined behavior(Attempting to destroy a locked mutex results in undefined behavior). Am I right? If I'm right, then, how to make it work right or how to safely and correctly destroy a mutex in Linux using pthread_mutex_destroy?

Answer Source

The POSIX spec for pthread_mutex_destroy() says:

It shall be safe to destroy an initialized mutex that is unlocked.

Which means that if thread B calls pthread_mutex_unlock() in the else clause of the if statement in foo_rele() then it's safe for thread A to call pthread_mutex_destroy() because it can only have gotten there after thread B's pthread_mutex_unlock() call has unlocked the mutex.

All of this is assuming that the reference counting is correct, such that some other thread cannot increment the count from 0 -> 1 after thread A has unlocked the mutex. In other words, at the point where the refcount drops to 0, there can't be another thread that might possibly call foo_hold().

APUE mentions this in the explanation right after the example code:

In this example, we have ignored how threads find an object before calling foo_hold. Even though the reference count is zero, it would be a mistake for foo_rele to free the object’s memory if another thread is blocked on the mutex in a call to foo_hold. We can avoid this problem by ensuring that the object can’t be found before freeing its memory. We’ll see how to do this in the examples that follow.