HOWTO detect that a pthreads thread has exited
This question came up in a discussion on the Poly/ML mailing list. How can you tell that a pthreads thread has exited, so that you can clean up any resources dedicated to that thread? Here is a program that demonstrates one approach. It is a cleaned up and enhanced version of some code I posted to the mailing list at the time, and thought might be useful for others.
The idea is to use a pthread_key_t
with a destructor function.
If a thread assigns a value to that key, then the destructor function is
called on the value when the thread exits.
/**
* pthread_destructor - Demonstration of using a pthread key to detect that a
* thread has exited.
*
* Written in 2013 by Jerry James <loganjerry@gmail.com>.
*
* To the extent possible under law, the author has dedicated all copyright
* and related and neighboring rights to this software to the public domain
* worldwide. This software is distributed without any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication along
* with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
/**
* Size of the user input buffer.
*/
#define DESTRUCTOR_BUFSIZ (1 << 10U)
/**
* Type used to initialize threads.
*/
struct destructor_init {
/**
* The condition variable used to make this thread exit.
*/
pthread_cond_t di_exit;
/**
* This thread's number.
*/
size_t di_number;
/**
* This thread's internal identifier.
*/
pthread_t di_thread;
/**
* Whether the thread is still running
*/
int di_running;
};
/**
* The One Big Lock that protects everything.
*/
static pthread_mutex_t destructor_lock = PTHREAD_MUTEX_INITIALIZER;
/**
* Key to per-thread values.
*/
static pthread_key_t destructor_key;
/**
* Buffer for reading input from the user.
*/
static char destructor_buffer[DESTRUCTOR_BUFSIZ];
/**
* Give the user a clue.
*
* @param[in] progname the name of the executable
*/
static void
destructor_usage (const char *progname)
{
fprintf (stderr, "Usage: %s num_threads\n\
where num_threads is between 0 and 1000, exclusive\n",
progname);
}
/**
* Destructor function run on the shared key when a thread exits.
*
* @param[in] value a pointer to the number of this thread
*/
static void
key_destructor (void *value)
{
size_t *number = (size_t *) value;
printf ("This is thread %zu signing off!\n", *number);
}
/**
* Function run by each child thread.
*
* @param[in] arg a pointer to the destructor_init structure for this thread
* @return @c NULL
*/
static void *
destructor_child (void *arg)
{
struct destructor_init *init = (struct destructor_init *) arg;
pthread_setspecific (destructor_key, &init->di_number);
pthread_mutex_lock (&destructor_lock);
pthread_cond_wait (&init->di_exit, &destructor_lock);
pthread_mutex_unlock (&destructor_lock);
init->di_running = 0;
return NULL;
}
int
main (int argc, char *argv[])
{
struct destructor_init *inits;
size_t i, num_children;
int err;
/* Get the number of child threads to spawn */
if (argc != 2)
{
destructor_usage (argv[0]);
return EXIT_FAILURE;
}
num_children = strtoul (argv[1], NULL, 0);
if (num_children < 1U || num_children > 999U)
{
destructor_usage (argv[0]);
return EXIT_FAILURE;
}
/* Set up the children array */
inits = (struct destructor_init *) calloc (num_children, sizeof (*inits));
if (inits == NULL)
{
fputs ("Out of memory.\n", stderr);
return EXIT_FAILURE;
}
/* Create the key for the thread-specific values */
err = pthread_key_create (&destructor_key, key_destructor);
if (err != 0)
{
errno = err;
printf ("pthread_key_create: %m\n");
free (inits);
return EXIT_FAILURE;
}
/* Spawn the children */
for (i = 0U; i < num_children; i++)
{
pthread_cond_init (&inits[i].di_exit, NULL);
inits[i].di_number = i;
inits[i].di_running = 1;
pthread_create (&inits[i].di_thread, NULL, destructor_child, &inits[i]);
}
/* Ask the user which thread should exit next */
for (;;)
{
size_t threadno;
char *endptr;
fputs ("Choose a thread, or ENTER to exit: ", stdout);
fflush (stdout);
if (fgets (destructor_buffer, DESTRUCTOR_BUFSIZ, stdin) == NULL)
break;
threadno = strtoul (destructor_buffer, &endptr, 0);
if (endptr == destructor_buffer)
{
if (destructor_buffer[0] == '\n' && destructor_buffer[1] == '\0')
break;
printf ("Enter a nonnegative integer less than %zu, or a blank line to exit.\n",
num_children);
}
else if (inits[threadno].di_running)
{
pthread_mutex_lock (&destructor_lock);
pthread_cond_signal (&inits[threadno].di_exit);
pthread_mutex_unlock (&destructor_lock);
pthread_join (inits[threadno].di_thread, NULL);
pthread_cond_destroy (&inits[threadno].di_exit);
}
else
{
printf ("Thread %zu has already exited.\n", threadno);
}
}
/* Kill the rest of the threads */
for (i = 0U; i < num_children; i++)
{
if (inits[i].di_running)
{
pthread_mutex_lock (&destructor_lock);
pthread_cond_signal (&inits[i].di_exit);
pthread_mutex_unlock (&destructor_lock);
pthread_join (inits[i].di_thread, NULL);
pthread_cond_destroy (&inits[i].di_exit);
}
}
/* Cleanup and exit */
pthread_key_delete (destructor_key);
free (inits);
return EXIT_SUCCESS;
}