mario::konrad
programming / C++ / sailing / nerd stuff
Multithreading: Tutorial 07: Thread Cancellation
© 2005 / Mario Konrad

Introduction

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.

Cancel a Thread

This chapter shows how to cancel a thread.

Basic

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.

Enable and Disable the Cancellation

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);

Release Resources

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!