0

It seems fairly common to have pthread mutexes that are intended to exist until the end of a program's lifetime. Often these are created using PTHREAD_MUTEX_INITIALIZER.

Here's a short but complete code example showing what I'm referring to:

#include <pthread.h>

#include <iostream>

void log(char const * const message) {
    static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

    pthread_mutex_lock(&mutex);
    std::cout << message << std::endl;
    pthread_mutex_unlock(&mutex);
}

struct Object final {
    Object() { log("Object::Object()"); }
    ~Object() { log("Object::~Object()"); }
};

Object const object;

int main(int const argc, const char * argv[]) {
    log("main()");

    // Here the program would enter a main loop, with log() potentially being
    // called from multiple threads at various times.
}

Output:

Object::Object()
main()
Object::~Object()

Error-checking and RAII wrapping for the lock are omitted for brevity.

This example includes a thread-safe (at least that's the intent) logging function that's intended to be available throughout the lifetime of the program, including during deinitialization of objects with static storage duration, possibly (although not in this case) across multiple translation units, meaning that deinitialization order may be indeterminate.

The issue is that there's no opportunity to safely destroy the mutex, as it may be needed at any point in the program's lifetime. (In practice I don't intend to have objects with static storage duration and nontrivial destructors, but I'm interested in addressing the issue nonetheless.)

The first question that arises is whether mutexes initialized using PTHREAD_MUTEX_INITIALIZER need to be destroyed using pthread_mutex_destroy(). At least some versions of the documentation include this wording:

In cases where default mutex attributes are appropriate, the macro PTHREAD_MUTEX_INITIALIZER can be used to initialize mutexes. The effect shall be equivalent to dynamic initialization by a call to pthread_mutex_init() with parameter attr specified as NULL, except that no error checks are performed.

This suggests that if pthread_mutex_destroy() is expected to be called on mutexes initialized using pthread_mutex_init(), then it's expected to be called on mutexes initialized using PTHREAD_MUTEX_INITIALIZER as well.

However, in searching online and here on Stack Overflow, I've found disagreement as to whether it's required. For example, here someone offers the following quote from a book on Linux development:

It is not necessary to call pthread_mutex_destroy() on a mutex that was statically initialized using PTHREAD_MUTEX_INITIALIZER.

On the other hand, in this thread it's argued that explicitly destroying such a mutex is in fact required.

I've also seen it argued that it's not necessary to clean up mutexes in these sorts of circumstances regardless of how they're initialized because the resources will be reclaimed anyway. (This would presumably be the same logic behind the 'construct on first use and deliberately leak memory' idiom sometimes used for singletons and other objects with static storage duration.)

I found a number of other threads that touch on the subject, with a mix of opinions on if/how mutexes should be destroyed. I'll also mention that I believe I've seen production code from credible sources that initializes mutexes with PTHREAD_MUTEX_INITIALIZER and never destroys them.

I've gone into some detail here as a matter of due diligence, but my question is (I think) fairly simple. It would be useful to have mutexes that are available from initialization to the very end of the program's lifetime. I suspect not cleaning up such mutexes wouldn't cause any problems, but that approach troubles me. And even though some say mutexes initialized using PTHREAD_MUTEX_INITIALIZER don't need to be cleaned up, that seems contrary to the documentation and to various claims made by others.

In summary, is there a safe and reasonable way to manage pthread mutexes that are intended to be available until the end of a program's lifetime? Is there any standard best practice here that I've failed to stumble upon in my search?

scg
  • 449
  • 1
  • 3
  • 11
  • 2
    Which gets released first? The memory occupied by the static variable `mutex` or the the global `Object`. This might be bordering on undefined behavior. It's also why I pretty much ban global objects from my team. Having code of any significance run after `main` returns is a debugging nightmare when something goes wrong. – selbie Nov 27 '19 at 22:59
  • I don't think there's an order or undefined behavior issue here because pthread_mutex_t is trivially destructable. If there's an issue, it's just that 'mutex' isn't cleaned up. I agree about global objects (in most cases at least), but for various reasons I'm still interested in the problem of creating a thread-safe function that can be safely called at any point in program execution (log() in this example). – scg Nov 27 '19 at 23:44
  • Why not use standard C++ threads and mutexes and let their internals handle such details? – Shawn Nov 28 '19 at 00:12
  • 1
    @shawn - I believe it's even more risky to replace `pthread_mutex_t` with `std::mutex` because the latter doesn't have a trivial destructor. If the mutex in the logging function destructs before the `Object` instance, you're going to have issues `~Object` attempts to log. That's why it **might** be safe to do what the OP is showing. I'll try to elaborate more when I answer. – selbie Nov 28 '19 at 00:43
  • 1
    @scg - Although my comment above speaks to my aversion of using global objects (as you indicate in your post too), I think what you have is safe and OK. I don't have time to write an full answer now, but I'll try to something a bit more formal tonight. – selbie Nov 28 '19 at 00:48
  • @Shawn: As selbie alluded to, std::mutex isn't (necessarily) trivially destructible, and so wouldn't be a suitable solution here. As to why I'm using pthread mutexes, I'm working in a context that uses pthreads. I think C++ thread features will generally be using the underlying thread model and therefore be compatible with it, but I'm still a little wary (perhaps irrationally) of 'mixing and matching' C++ thread features with pthread features. – scg Nov 28 '19 at 00:52

1 Answers1

1

Since initializing with PTHREAD_MUTEX_INITIALIZER is equivalent to calling pthread_mutex_init, it is OK to call pthread_mutex_destroy to destroy such a mutex.

However, calling pthread_mutex_destroy is not required; the resources will be reclaimed by the OS at program exit. Since it is an object with a trivial destructor, it is not destroyed as part of static cleanup at program exit, and so is safe to use until the end of the program.

Anthony Williams
  • 66,628
  • 14
  • 133
  • 155