Cancelling a Thread Using Pthread_Cancel:Good Practice or Bad

Cancelling a thread using pthread_cancel : good practice or bad

In general thread cancellation is not a really good idea. It is better, whenever possible, to have a shared flag, that is used by the threads to break out of the loop. That way, you will let the threads perform any cleanup they might need to do before actually exiting.

On the issue of the threads not actually cancelling, the POSIX specification determines a set of cancellation points ( man 7 pthreads ). Threads can be cancelled only at those points. If your infinite loop does not contain a cancellation point you can add one by calling pthread_testcancel. If pthread_cancel has been called, then it will be acted upon at this point.

When to use pthread_cancel and not pthread_kill?

I would use neither of those two but that's just personal preference.

Of the two, pthread_cancel is the safest for terminating a thread since the thread is only supposed to be affected when it has set its cancelability state to true using pthread_setcancelstate().

In other words, it shouldn't disappear while holding resources in a way that might cause deadlock. The pthread_kill() call sends a signal to the specific thread, and this is a way to affect a thread asynchronously for reasons other than cancelling it.

Most of my threads tends to be in loops doing work anyway and periodically checking flags to see if they should exit. That's mostly because I was raised in a world when pthread_kill() was dangerous and pthread_cancel() didn't exist.

I subscribe to the theory that each thread should totally control its own resources, including its execution lifetime. I've always found that to be the best way to avoid deadlock. To that end, I simply use mutexes for communication between threads (I've rarely found a need for true asynchronous communication) and a flag variable for termination.

pthread_cancel and cancellation point

To have a "cancellation point" you need to use pthread_setcancelstate() to disable cancellation at the start of your thread function and then enable it when you want. When a new thread is spawned, it has the cancel state "enabled" meaning it can be canceled immediately at any time.

Perhaps more to the point, you probably shouldn't use pthread_cancel() at all. For more on that, see here: Cancelling a thread using pthread_cancel : good practice or bad

How to avoid a memory leak by using pthread_cancel?

You have correctly identified a drawback of using pthread_cancel(): any resource not released/freed by the thread cleanup routine will subsequently leak. In your case, it appears that the thread library, itself, might have allocated some memory that isn't being freed.

A better approach, IMO, would be to create a mechanism for notifying the threadhandler thread that it should terminate. For example

static volatile sig_atomic_t done = 0;
...
void cleanup()
{
done = 1;
}

void* threadhandler(void* arg)
{
while (!done)
fprintf(stderr, "Run\n");
return NULL;
}

How to terminate threads cleanly in C?

You must not call pthread_exit() in the cleanup functions, because pthread_exit() will also call the cleanup function registered for the thread.

So, in your program, the cleanup function is called recursively and the threads never exit.

About the kill from another terminal, the command kill -9 and the pid of the process should always work because SIGKILL can't be ignored nor caught.

And in the signal handler function, you have to use async-signal-safe functions, printf() isn't async-signal-safe.

Another way to wait for a signal in the main thread is to use sigwait() or sigwaitinfo() instead of pause(), like you did for SIGALARM in a thread. So it won't need to register a handler function, but it will need to block the signals to be caught in all threads.

EDIT: To answer your last comment.

Exiting the threads task2() and task3() with a flag seems to be complex, because the main thread have to send SIGALRM to task2 in order to wake it up, and also signal the condition in order to wake up task3.

I modified your code to try to use a flag, but i may have missed an eventual problem because synchronizing threads may be complex.

In the case of your program, I haven't enough knwoledge to say if it is better to use pthread_cancel() and pthread_testcancel(), or to use flags. However, pthread_cancel() seems to be able to cancel without synchronization problems, threads that are waiting for signals or for a condition.

Using a flag, for task3, there could be the following problem:

  1. task3 check the flag that is 0
  2. main thread set the flag to 1
  3. main thread signal the condition
  4. task3 begin to wait for the condition

In this case, thread task3 won't exit, because it wasn't waiting when the condition was signaled. I'am not sure, but this problem is maybe avoided by protecting the flag with the same mutex we use for the condition. Because when the flag will be set and the condition signaled, task3 will be waiting for the condition or doing work out of the critical section.

I don't know if there may be a problem for task2, for example if the signal is lost due to an internal problem, but normally, the signal will be pending.

Here is the code of my test. I placed 1 as argument for the function pthread_cleanup_pop(), to make the threads execute the cleanup functions.

#include<stdlib.h>
#include<stdio.h>
#include<signal.h>
#include<stdint.h>
#include<pthread.h>

#define FALSE 0
volatile sig_atomic_t g_new_pic_flag=FALSE;
pthread_cond_t g_new_pic_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t g_new_pic_m = PTHREAD_MUTEX_INITIALIZER;
volatile int g_shutdown_task_3 = 0;

volatile int g_shutdown_task_1_2 = 0;
pthread_mutex_t g_shutdown_mutex = PTHREAD_MUTEX_INITIALIZER;
/* FUNCTION DECLARATION */

/*We define thread exit functions so that each pin
is lowered by the thread in which it is used avoiding
race condition between the signal handler of the main thread
and the other threads*/
void exitingThreadTask1(void* arg);
void exitingThreadTask2(void* arg);
void exitingThreadTask3(void* arg);

void* task1(void *arg); //thread function for the motion sensor
void* task2(void *arg); //thread function for the temperature reading
void* task3(void *arg); //thread function to post data on IOT platforms

/*Signal handler to return from pause*/
void sig_handler(int signo);

void err_exit(char err, char *msg) {
printf("\nError: %s\n",msg);
exit(1);
}

int main()
{
int err;
sigset_t omask, mask;
pthread_t thread_motionSensor;
pthread_t thread_tempReading;
pthread_t thread_platformPost;

printf("Created threads IDs\n");
/*
if (wiringPiSetup()<0)
{
printf("WiringPi error\n");
return -1;
}
*/
printf("WiringPi is ok\n");

if (signal(SIGQUIT, sig_handler)==SIG_ERR)
printf("Error on recording SIGQUITHANDLER\n");
if (signal(SIGINT, sig_handler)==SIG_ERR)
printf("Error on recording SIGQUITHANDLER\n");
if (signal(SIGTERM, sig_handler)==SIG_ERR)
printf("Error on recording SIGQUITHANDLER\n");

/*Create a new mask to block all signals for the following thread*/
sigfillset(&mask);
pthread_sigmask(SIG_SETMASK, &mask, &omask);
printf("Trying to create threads\n");
if ((err = pthread_create (&thread_motionSensor, NULL, task1, NULL))!=0)
{
printf("Thread 1 not created: error %d\n", err);
err_exit((const char)err, "pthread_create error");
}
printf("Thread 1 created. Trying to create Thread 2\n");
if((err = pthread_create (&thread_tempReading, NULL, task2, NULL))!=0)
{
printf("Thread 2 not created: error %d\n", err);
err_exit((const char)err, "pthread_create error");
}
printf("Thread 2 created. Trying to create Thread 3\n");
if ((err = pthread_create (&thread_platformPost, NULL, task3, NULL))!=0)
{
printf("Thread 3 not created: error %d %d\n", err);
err_exit((const char)err, "pthread_create error");
}
printf("Thread 3 created\n");
/*The main thread must block the SIGALRM but catch SIGINT
SIGQUIT, SIGTERM, SIgkILL*/
sigemptyset(&omask);
sigaddset(&omask, SIGINT);
sigaddset(&omask, SIGQUIT);
sigaddset(&omask, SIGKILL);
sigaddset(&omask, SIGTERM);

pthread_sigmask(SIG_UNBLOCK, &omask, NULL);
printf("Main thread waiting for signal\n");
pause();
printf("Exit signal received: cancelling threads\n");


pthread_mutex_lock(&g_shutdown_mutex);
g_shutdown_task_1_2 = 1;
pthread_mutex_unlock(&g_shutdown_mutex);
pthread_mutex_lock(&g_new_pic_m);
g_shutdown_task_3 = 1;
pthread_cond_signal(&g_new_pic_cond);
pthread_mutex_unlock(&g_new_pic_m);

pthread_kill(thread_tempReading,SIGALRM);


pthread_join(thread_motionSensor, NULL);
pthread_join(thread_tempReading, NULL);
pthread_join(thread_platformPost, NULL);
printf("Exiting from main thread and process\n");
exit(0);
}

void* task1(void *arg)
{
//INITIALIZING
pthread_cleanup_push(exitingThreadTask1, NULL);
while(1)
{
pthread_mutex_lock(&g_shutdown_mutex);
if(g_shutdown_task_1_2) {
pthread_mutex_unlock(&g_shutdown_mutex);
break;
}
pthread_mutex_unlock(&g_shutdown_mutex);
//do stuff1
sleep(1);
}
pthread_cleanup_pop(1);
pthread_exit(0);

}

void* task2(void *arg)
{
static const unsigned char schedule_time = 5;
int signo, err;
/*
We set a local mask with SIGALARM for the function sigwait
All signals have already been blocked
*/
sigset_t alarm_mask;
sigemptyset(&alarm_mask);
sigaddset(&alarm_mask, SIGALRM);
alarm(schedule_time);
pthread_cleanup_push(exitingThreadTask2, NULL);
while (1)
{
pthread_mutex_lock(&g_shutdown_mutex);
if(g_shutdown_task_1_2) {
pthread_mutex_unlock(&g_shutdown_mutex);
break;
}
pthread_mutex_unlock(&g_shutdown_mutex);

err = sigwait(&alarm_mask, &signo); //signo == SIGALRM check
if (err!=0)
err_exit(err, "sigwait failed\n");

pthread_mutex_lock(&g_shutdown_mutex);
if(g_shutdown_task_1_2) {
pthread_mutex_unlock(&g_shutdown_mutex);
break;
}
pthread_mutex_unlock(&g_shutdown_mutex);

//do stuff
alarm(schedule_time);
}
pthread_cleanup_pop(1);
pthread_exit(0);
}

void* task3(void *arg)
{
pthread_cleanup_push(exitingThreadTask3, NULL);
while(1)
{
pthread_mutex_lock(&g_new_pic_m);
if(g_shutdown_task_3) {
pthread_mutex_unlock(&g_new_pic_m);
break;
}
while(g_new_pic_flag==FALSE)
{
if(g_shutdown_task_3) break;

pthread_cond_wait(&g_new_pic_cond, &g_new_pic_m);

if(g_shutdown_task_3) break;
}
if(g_shutdown_task_3) {
pthread_mutex_unlock(&g_new_pic_m);
break;
}
pthread_mutex_unlock(&g_new_pic_m);
//do stuff
}
pthread_cleanup_pop(1);
pthread_exit(0);

}

void exitingThreadTask1(void* arg)
{
printf("Thread of task 1 exiting\n");
//digitalWrite(OUTPIN, LOW);
//digitalWrite(INPIN, LOW);
printf("Pins lowered\n");
}

void exitingThreadTask2(void* arg)
{
printf("Thread of task 2 exiting\n");
//digitalWrite(DHTPIN, LOW);
printf("Pin lowered\n");
}

void exitingThreadTask3(void* arg)
{
printf("Thread of task 3 exiting\n");
}

void sig_handler(int signo)
{
return;
}

cancelling std::thread using native_handle() + pthread_cancel()

No, I don't think that you will not have additional problems than:

  • not being portable
  • having to program _very_very_ carefully that all objects of the cancelled thread are destroyed...

For example, the Standard says that when a thread ends variables will be destroyed. If you cancel a thread this will be much harder for the compiler, if not impossible.

I would, therefore recommend not to cancel a thread if you can somehow avoid it. Write a standard polling-loop, use a condition variable, listen on a signal to interrupt reads and so on -- and end the thread regularly.

How can I cancel a thread using C++?

Although there is no exact replacement for pthread_cancel, you can come close with jthread.

The class jthread represents a single thread of execution. It has the same general behavior as std::thread, except that jthread automatically rejoins on destruction, and can be cancelled/stopped in certain situations.

This will not interrupt the thread, but still relies on "cooperative" mechanisms. The running jthread will have to keep polling it's stop token and stop by itself.

A good example can be found here.

    std::jthread sleepy_worker([] (std::stop_token stoken) {
for(int i=0; i < 10; i++) {
std::this_thread::sleep_for(300ms);
if(stoken.stop_requested()) {
std::cout << "Sleepy worker is requested to stop\n";
return;
}
std::cout << "Sleepy worker goes back to sleep\n";
}
});
sleepy_worker.request_stop();
sleepy_worker.join();

But to bluntly answer your question: no, there is (currently, c++20) no portable way of cancelling a thread.

P.S.: Actually "killing" a thread (or anything for that matter) is something that should usually be avoided. As such I personally doubt you'll ever see a mechanism for it in ISOC++.

pthread_cancel() on linux leads to exception/coredump, why?

Thread cancellation points and C++ do not go well with each other. Think of what would happen with local variables in the thread stack when the thread is cancelled, will their destructors be called.

Historically, there has been some debate about this, and many pthread implementations chose differently. Some would even implement the cancellation with the same mechanism as a C++ exception, so that the stack will be nicely unwound. But then, a well placed catch will mess things around...

But then, pthread_cancel is a POSIX construction, and thus is only well defined for C.

That said, compilers try to do their best. In your case, it is probably a bug in your compiler or some library (you don't say what compiler version and what compiler commands you are using...). Your code works fine for me (GCC 7.1 and Clang 4.0.1).

However if I add a try/catch like this, it fail just like yours:

void* tf(void*arg) {
try {
//your code
} catch (...) {
return NULL;
}
}

If however, I rethrow the exception at the end of the catch it will work fine again:

void* tf(void*arg) {
try {
//your code
} catch (...) {
throw;
}
}

Which proves that in my C++ compiler pthread_cancel uses the same mechanism as C++ exceptions. My guess is that when the thread is cancelled in C++ mode, it throws an internal exception, and it expects to catch it at the parent of the thread function. But if you return normally from a thread function, when that thread has been cancelled, then this message is shown.

And since you are not doing anything of this, there are several explanations:

  • You are using a compiler that does not support C++ and pthread cancellation.
  • A bug in your version of the compiler/library.
  • You are mixing different versions of the compiler/library. Or maybe you are mixing C and C++ libraries.

PS 1: I checked adding a local variable with a non trivial destructor to the thread function, and I confirm that the destructor is called when the thread finishes.

PS 2: I checked calling pthread_exit() from the thread function, without cancellations, and the local destructors are also called. If I do the try/catch(...) around pthread_exit() it fails with the same FATAL: exception not rethrown. So pthread_exit() and pthread_cancel() use the same underlying mechanism.

PS 3: All that looks very nice, but thread cancellation can still occur at any point: if the thread cancellation happens in the middle of a cout.operator<<() then any further writing to that stream (from the local destructors for example) will not work at all.



Related Topics



Leave a reply



Submit