This tutorial is about thread cancellation. In many cases thread, once created, work forever. At least as long as the application runs.
The main problem are the aquired resources (e.g. memory, mutexes, sockets, etc.). Resources are freed by the operating system when a process terminates but not if a thread is terminated.
NOTE: thread cancellation may not work properly on every platform, therefore not really portable.
This chapter shows how to cancel a thread.
POSIX threads cancel (voluntarly) only on so called cancellation points. Example:
void * thread_function(void * ptr)
{
// ...
while (1) {
// ...
usleep(10000); // 10msec
}
// ...
pthread_exit(0);
}
void start_thread(void)
{
pthread_t tid;
pthread_create(&tid, 0, thread_function, 0);
usleep(1000000); // 1sec
pthread_cancel(tid);
pthread_join(tid, 0);
}
In this example is usleep in the function thread_function
a cancellation point. It runs just as intended, the thread gets cancelled after a second and start_thread
returns.
pthread_cancel
does not acutally cancel the thread, it sends a cancellation request to the thread. If the thread is running or not waiting on a non-cancellation point it will terminate.
It is possible to configure the thread not to cancel at all. This is achieved using the function pthread_setcancelstate
. Example:
void * thread_function(void * ptr)
{
// ...
// begin of a very important passage and not to be canceled:
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0);
// ...
for (int i = 0; i < 10; ++i) {
// ...
usleep(100000); // 100msec
}
// ...
// end of the very important passage:
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0);
pthread_exit(0);
}
void start_thread(void)
{
pthread_t tid;
pthread_create(&tid, 0, thread_function, 0);
usleep(500000); // 500msec
pthread_cancel(tid);
pthread_join(tid, 0);
}
The function pthread_setcancelstate
takes a second argument in which, if not NULL
the current state is saved. To be used like this for example:
int old_state;
// ...
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state);
// ...
pthread_setcancelstate(old_state, 0);
Consider the following example:
class Foo
{
public:
Foo() { printf("%s\n", __PRETTY_FUNCTION__); }
~Foo() { printf("%s\n", __PRETTY_FUNCTION__); }
};
void * thread_function(void * ptr)
{
// aquire resources
Foo * foo = new Foo;
// work with resources
for (int i = 0; i < 20; ++i) {
// ...
usleep(10000); // 10msec
}
// release resources
delete foo;
// exit
pthread_exit(0);
}
void start_thread(void)
{
pthread_t tid;
pthread_create(&tid, 0, thread_function, 0);
usleep(100000); // 100msec
pthread_cancel(tid);
pthread_join(tid, 0);
}
The problem is when the thread is cancelled, the aquired resource (foo in the example) is never freed. This results in a memory hole.
A possible solution would be to disable the cancellation of the thread, like seen above. This is, in most cases, not desired. The solution to the problem is to install some kind of exit handler which is called if the thread gets terminated early. Example:
class Foo
{
public:
Foo() { printf("%s\n", __PRETTY_FUNCTION__); }
~Foo() { printf("%s\n", __PRETTY_FUNCTION__); }
};
Foo * foo = 0;
void exit_handler(void *)
{
if (foo != 0) {
delete foo;
foo = 0;
}
}
void * thread_function(void * ptr)
{
// save cancel type
int old_type;
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &old_type);
pthread_cleanup_push(&exit_handler, 0);
// aquire resources
foo = new Foo;
// work with resources
for (int i = 0; i < 20; ++i) {
// ...
usleep(10000); // 10msec
}
// release resources
delete foo;
foo = 0;
// restore cancel type
pthread_cleanup_pop(0);
pthread_setcanceltype(old_type, 0);
// exit
pthread_exit(0);
}
void start_thread(void)
{
pthread_t tid;
pthread_create(&tid, 0, thread_function, 0);
usleep(100000); // 100msec
pthread_cancel(tid);
pthread_join(tid, 0);
}
The function exit_handler releases the aquried resource (foo) in case of early termination of the thread, no more memory leaks. The trick is to configure the thread to use the deferred exit function with PTHREAD_CANCEL_DEFERRED
, and pushing the desired function with pthread_cleanup_push
. It is also possible to have multiple exit handlers.
To keep the code short it is also possible to call the exit handler either way:
void * thread_function(void * ptr)
{
// save cancel type
int old_type;
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &old_type);
pthread_cleanup_push(&exit_handler, 0);
// aquire resources
foo = new Foo;
// work with resources
for (int i = 0; i < 20; ++i) {
// ...
usleep(10000); // 10msec
}
// restore cancel type
pthread_cleanup_pop(1);
pthread_setcanceltype(old_type, 0);
// exit
pthread_exit(0);
}
Changing the argument of pthread_cleanup_pop
from 0 to 1 causes the invocation of the pushed function (exit_handler in this case, which releases the resource. Duplication of code may be avoided this way.
The only disadvantage in the example above is the fact that the resource foo has to be global. This problem is solved rather easly:
class Foo
{
public:
Foo() { printf("%s\n", __PRETTY_FUNCTION__); }
~Foo() { printf("%s\n", __PRETTY_FUNCTION__); }
};
void exit_handler(void * ptr)
{
if (ptr != 0) {
Foo ** f = (Foo **)ptr;
if (*f != 0) delete *f;
}
}
void * thread_function(void * ptr)
{
Foo * foo = 0;
// save cancel type
int old_type;
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &old_type);
pthread_cleanup_push(&exit_handler, &foo);
// aquire resources
foo = new Foo;
// work with resources
for (int i = 0; i < 20; ++i) {
// ...
usleep(10000); // 10msec
}
// restore cancel type
pthread_cleanup_pop(1);
pthread_setcanceltype(old_type, 0);
// exit
pthread_exit(0);
}
No need for global variables!