2

I want to ask the proper usage of pthread_mutex_t.

I understand that you can either initialize by using the pthread_mutex_init function or setting it equal to PTHREAD_MUTEX_INITIALIZERand that pthread_mutex_destroy "destroys" the mutex.

I am wondering about if it is okay not to call pthread_mutex_destroy and in what circumstances it is necessary? I found the specification here: here.

Here is my specific use case:

struct entries; 

struct hash_table {
  struct hash_table_entry entries[HASH_TABLE_CAPACITY];
  pthread_mutex_t fine_grain[HASH_TABLE_CAPACITY];
};

struct hash_table *hash_table_create() {
  struct hash_table *hash_table = calloc(1, sizeof(struct hash_table));
  for (size_t i = 0; i < HASH_TABLE_CAPACITY; ++i) {
  ...
  hash_table->fine_grain[i] = PTHREAD_MUTEX_INITIALIZER;
}
  return hash-table
}

The reason I ask is that the teaching assistant in charge of the project has stated:

After using a lock, make sure you will destroy it, we will check for memory leaks

This is not referencing the allocated hash table, but the individual locks, so I am wondering why pthread_mutex_destroy might ever be necessary.

user129393192
  • 797
  • 1
  • 8
  • Your link *does* talk about memory allocation (indirectly). Under *"The `pthread_mutex_init()` function shall fail if:"* it says `[ENOMEM] Insufficient memory exists to initialize the mutex.` – Weather Vane May 06 '23 at 21:42
  • So does that mean that even the macro `PTHREAD_MUTEX_INITIALIZER` will allocate memory? – user129393192 May 06 '23 at 21:48
  • The link says the macro method allocates memory statically, not dynamically. Therefore it can't be freed. *why pthread_mutex_destroy might ever be necessary?* because your teaching assistant said to. I would presume that the function knows what method was used to initialise it, and there may be other reasons to destroy it besides memory allocation. – Weather Vane May 06 '23 at 22:13
  • I found this: http://git.savannah.gnu.org/cgit/hurd/libpthread.git/tree/sysdeps/generic/pt-mutex-destroy.c , so it seems it is correct that it is not necessary when using `PTHREAD_MUTEX_INITIALIZER` – user129393192 May 06 '23 at 22:18
  • Which means that it is harmless to destroy. So do as the teacher says and don't argue. – Weather Vane May 06 '23 at 22:20
  • That is a fair point. Do you know if the compiler is typically able to peer into POSIX API calls like this and optimize them away if it finds them unnecessary, like `pthread_mutex_destroy` in this case? – user129393192 May 06 '23 at 22:25
  • IDK but the first link also says *An implementation may cause `pthread_mutex_destroy()` to set the object referenced by mutex to an invalid value.* So you can't be sure that it's safe to ignore, and take matters into you own hands. You might be good (better than the teacher?) but pride comes before a fall. – Weather Vane May 06 '23 at 22:28
  • 1
    @WeatherVane: Your comments are not helpful. OP does not indicate they plan to violate instructions, nor would it be any business of yours if they choose to do so. They are asking questions to add to their knowledge, and that is useful and ought to be encouraged. It is better to fully know why one is doing something rather than merely following orders without understanding. – Eric Postpischil May 06 '23 at 22:36
  • @WeatherVane I'm not sure why you say ignore. I was merely asking about the compiler, if it would optimize the function call away if it sees it does nothing. – user129393192 May 06 '23 at 22:44
  • That macro `PTHREAD_MUTEX_INITIALIZEE` does not use dynamic allocation, does not mean `pthread_mutex_lock` does not use it. Is your question "does pthread_mutex_destroy frees any memory"? I believe the "necessary" is confusing in your question - You can look at https://stackoverflow.com/questions/36584062/should-i-free-memory-before-exit . You do not have to free memory at all if you want. – KamilCuk May 07 '23 at 00:43
  • I am asking when it is necessary to call `pthread_mutex_destroy` @KamilCuk since it seems that the macro `PTHREAD_MUTEX_INITIALIZER` does not require "destruction" but `pthread_mutex_init` may require it in some instances – user129393192 May 07 '23 at 00:50
  • It's hard to answer that - nothing is necessary, that is opinion based. You have to state your goals to know what is necessary. Do you want to write code that frees allocated memory? Then calling pthread_mutex _may_ be necessary, you would have to see the underlying implementation. `PTHREAD_MUTEX_INITIALIZER does not require "destruction"` sure, what about `mutex_lock`? It can easily have `if (mutex == PTHREAD_MUTEX_INITIALIZER) { mutex = malloc() }`. I posted a link to a question - freeing memory is _not_ necessary, ex on linux, you can just not use free at all. – KamilCuk May 07 '23 at 00:59
  • Yes, I want to free memory, if you take a look at my original question, I do not want memory leaks. `pthread_mutex_lock` and `pthread_mutex_unlock` are not relevant in this discussion. I am asking for a clear cut answer on this on when it is appropriate to call `pthread_mutex_destroy`. – user129393192 May 07 '23 at 01:07
  • `are not relevant in this discussion` Well, but they are, then can allocate memory and initialize the mutex. Are we assuming they are never called? Yes, if you have a variable `a = PTHREAD_MUTEX_INITIALIZER` and you do nothing with it, then not calling `pthread_mutex_destroy` will not cause a memory leak. In other cases, it really comes down to what underlying implementation of pthread you are using. On freertos, mutex is actually initialized inside _lock() and then _destroy() calls like "free". – KamilCuk May 07 '23 at 01:14
  • Note that `PTHREAD_MUTEX_INITIALIZER` expands to an *initializer*, which is not necessarily a value literal. Generally speaking, it is not safe to use that in an assignment expression such as your `hash_table->fine_grain[i] = PTHREAD_MUTEX_INITIALIZER`. I'm moderately surprised that that's working for you, in fact. POSIX doesn't even require it to work for initializing automatic variables (though in most implementations it does), but only for variables with static storage duration. – John Bollinger May 08 '23 at 16:52
  • @JohnBollinger Wouldn't the members of a global `struct` be considered static storage duration? – user129393192 May 08 '23 at 17:37
  • @user129393192, the problem in your case is not about storage duration. It's about the difference between initialization and assignment. They use similar syntax, but they are not the same thing. Initialization (in this sense) happens only in object declarations. – John Bollinger May 08 '23 at 18:28
  • How could there be a difference when they are both using `=` @JohnBollinger ? I don't really understand the circumstances that would cause "initialization" to fail on assignment. – user129393192 May 08 '23 at 20:17
  • 1
    @user129393192: Re “How could there be a difference when they are both using `=`”: In the formal grammar given in the C standard, the `=` in an assignment is followed by an *assignment-expression*, and the `=` in a declarator is followed by an *initializer*. As one example, `{ 3 }` fits the grammar of an *initializer* and does not fit the grammar of an *assignment-expression*. So using it in a declaration of an appropriate type would be fine, but using it in an assignment would yield a diagnostic message about a syntax/grammar error. – Eric Postpischil May 08 '23 at 20:26
  • @user129393192, `x*=y` means something altogether different from `x=*y`, even though they use the same four symbols. In any given context, they cannot both be valid. – John Bollinger May 08 '23 at 20:36

2 Answers2

2

if it is okay not to call pthread_mutex_destroy and in what circumstances it is necessary?

I do not want memory leaks.

If you have just a variable pthread_mutex_t a = PTHREAD_MUTEX_INITIALIZER; and you do nothing with it then calling pthread_mutex_destroy is not necessary.

If you call pthread_mutex_lock(&a) on it, the lock function can potentially allocate memory or other resources. In such case, you should call pthread_mutex_destroy(&a) to make sure the implementation frees that memory.

But yes - there may and do exist implementations that do not allocate memory for mutexes. This it not a restriction, there may exist implementations that do allocate memory for mutexes upon calling _lock(), so to not have any leaks on such implementatin you should call _destroy().

The POSIX specification you linked has a whole section discussing static initialization for mutexes. Newer version is available at https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_destroy.html . In particular, it mentions:

Yet the locking performance question is likely to be raised for machines that require mutexes to be allocated out of special memory. Such machines actually have to have mutexes and possibly condition variables contain pointers to the actual hardware locks. For static initialization to work on such machines, pthread_mutex_lock() also has to test whether or not the pointer to the actual lock has been allocated. If it has not, pthread_mutex_lock() has to initialize it before use. The reservation of such resources can be made when the program is loaded, and hence return codes have not been added to mutex locking and condition variable waiting to indicate failure to complete initialization.

This runtime test in pthread_mutex_lock() would at first seem to be extra work; an extra test is required to see whether the pointer has been initialized. On most machines this would actually be implemented as a fetch of the pointer, testing the pointer against zero, and then using the pointer if it has already been initialized. While the test might seem to add extra work, the extra effort of testing a register is usually negligible since no extra memory references are actually done. As more and more machines provide caches, the real expenses are memory references, not instructions executed.

Kevin Ji
  • 10,479
  • 4
  • 40
  • 63
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • FWIW, the OP's link http://git.savannah.gnu.org/cgit/hurd/libpthread.git/tree/sysdeps/generic/pt-mutex-destroy.c clearly shows code that **does** free memory unless the mutex is a recursive or error-checking mutex, so if that's the `libc` in use, `pthread_mutex_t a = PTHREAD_MUTEX_INITIALIZER;` could very well leak if `pthread_mutex_destroy()` is not called. And that's just **one** example. Note this in your link: **"An implementation may cause pthread_mutex_destroy() to set the object referenced by mutex to an invalid value."** One way to do that is `free()` mutex-related resources. – Andrew Henle May 07 '23 at 01:32
  • How is it that valgrind shows that there is no memory leak in that case @AndrewHenle on a Linux machine? – user129393192 May 07 '23 at 01:38
  • And as I think about it, a POSIX-compliant implementation of C is even free to put calls to `malloc()` or similar into `PTHREAD_MUTEX_INITIALIZER` under C's abstract-machine "as-if" rule and do dynamic allocation in static initialization. – Andrew Henle May 07 '23 at 01:38
  • @user129393192 That link is likely not the code running in a Linux `libc.so`. N.B. it's **hurd/libpthread.git**. – Andrew Henle May 07 '23 at 01:39
  • 1
    `How is it that valgrind shows that there is no memory leak in that case @AndrewHenle on a Linux machine?` You are running Linux, the link to source code is Hurd. I do not seee glibc calling free, but it does unlinks the mutex from some thread storage. – KamilCuk May 07 '23 at 01:43
1

TLDR:

Pedantically, you need to call pthread_mutex_destroy() for every initialized mutex if you need to be absolutely certain no resources are leaked under any circumstances.

In reality, most times it won't matter, either because there's no way to call pthread_mutex_destroy(), or you know your implementation doesn't use dynamic memory in mutexes, or the process is exiting and you don't really care if it leaks.

Full Answer:

To expand on @KamilCuk's answer, from his link to POSIX's documentation for pthread_mutex_destroy():

An implementation may cause pthread_mutex_destroy() to set the object referenced by mutex to an invalid value.

One way to do that is for an implementation to call free() for mutex resources allocated during the creation and use of the mutex. Nothing rules out an implementation using dynamic allocation for any mutex - an implementation is even free under the "as-if" rules of C's abstract machine to use a PTHREAD_MUTEX_INITIALIZER that performs dynamic allocation even for static initialization of static variables at file scope, where normally C would not allow something like

static some_type *var = malloc( sizeof( *var ) );

(If C++ can create new objects at file scope, it'd be almost trivial for a C implementation to use something similar internally, even if it flags your attempts to do so as C errors.)

So pedantically, if you want to strictly avoid leaking any memory, pthread_mutex_destroy() must be called for every mutex you initialize.

The reality is different.

First, many (most?) of the time you will use a mutex, there will be no easy way to destroy it. For example, it can be a static with limited scope deep inside a function used to protect a critical section of code - there's no way to reliably call pthread_mutex_destroy() on such a mutex. Even if you register an atexit() function to destroy the mutex, that function won't be called in all ways a process can exit.

Second, most implementation of standard mutexes don't allocate any dynamic memory.

Third, many (most?) uses of mutexes are meant to exist for the life of the program - they only need to be destroyed if the program is exiting. And if the program is exiting, there's no functional impact if they're not destroyed.

But if you're creating structs dynamically and initializing mutexes in the structs with pthread_mutex_init(), you really should run pthread_mutex_destroy() on them when you free() the struct even if you know your use of your implementation's mutex doesn't do any dynamic allocation - someone might change the mutex to something more complex like an error-checking or robust mutex that might very well dynamically allocate memory on your implemenation, the code could be ported to another implementation, or the implementation itself could change. Get it right and don't open up that window for a completely unnecessary future bug.

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56