user2970017
user2970017

Reputation: 11

Cancelling boost thread from another

Is there a way to cancel a boost::thread from another as in the following?:

boost::thread* thread1(0);
boost::thread* thread2(0);

thread2 = new boost::thread([&](){
   //some expensive computation that can't be modified
  if(thread1)
    thread1->interrupt();  
});

thread1 = new boost::thread([&]() {
  //some other expensive computation that can't be modified
  if(thread2)
    thread2->interrupt();  
});

thread1->join();
thread2->join();

delete thread1;
delete thread2;

Right now both expensive computations finish without being interrupted. I had figured the joins would be treated as an interruption point, and the main thread would continue after one of the two expensive computations completed.

Upvotes: 0

Views: 670

Answers (2)

villapx
villapx

Reputation: 1893

A non-portable solution for POSIX-compliant systems (e.g. Linux) would be to use pthread_cancel() and then pthread_join() on the Boost thread's native_handle() member, which is of type pthread_t (again, only on POSIX-compliant systems. I can't speak for other systems, like Windows).

Also, you must use a boost::scoped_thread instead of just a boost::thread so that you can "override" (not in the OO-sense) the join/detach behavior that Boost will do when the thread is destroyed. This is necessary because when you call pthread_cancel then pthread_join on a boost::thread, the boost::thread object is still 'joinable' (i.e. boost::thread::joinable() returns true), and so the destructor will exhibit undefined behavior, per the documentation.

With all that being said, if a platform-dependent solution for cancelling threads like this is necessary in your application, I'm not sure there's much to be gained from using boost::threads over plain-old pthreads; still, I suppose there may be a use case for this.

Here's a code sample:

// compilation: g++ -pthread -I/path/to/boost/include -L/path/to/boost/libs -lboost_thread main.cpp

#include <cstdio>
#include <pthread.h>
#include <boost/thread/scoped_thread.hpp>

typedef struct pthreadCancelAndJoin
{
    void operator()(boost::thread& t)
    {
        pthread_t pthreadId = t.native_handle();
        int status = pthread_cancel(pthreadId);
        printf("Cancelled thread %lu: got return value %d\n", pthreadId, status);

        void* threadExitStatus;
        status = pthread_join(pthreadId, &threadExitStatus);
        printf("Joined thread %lu: got return value %d, thread exit status %ld\n",
               pthreadId, status, (long)threadExitStatus);
    }
} pthreadCancelAndJoin;

void foo()
{
    printf("entering foo\n");
    for(int i = 0; i < 2147483647; i++) printf("f");  // here's your 'expensive computation'
    for(int i = 0; i < 2147483647; i++) printf("a");
    printf("foo: done working\n");  // this won't execute
}

int main(int argc, char **argv)
{
    boost::scoped_thread<pthreadCancelAndJoin> t1(foo);
    pthread_t t1_pthread = t1.native_handle();

    sleep(1);  // give the thread time to get into its 'expensive computation';
               //   otherwise it'll likely be cancelled right away

    // now, once main returns and t1's destructor is called, the pthreadCancelAndJoin
    //   functor object will be called, and so the underlying p_thread will be cancelled
    //   and joined

    return 0;
}

pthread_cancel() will cancel your thread when it reaches a "cancellation point" (assuming the cancel type and cancel state are at their default values, which is the case for boost::thread objects); see the pthreads man page for a list of all cancellation points. You'll notice that those cancellation points include many of the more common system calls, like write, read, sleep, send, recv, wait, etc.

If your 'expensive computation' includes any of those calls down at its lowest level (e.g. in the code sample, printf eventually calls write), it will be cancelled.

Best of all, Valgrind reports no memory leaks or memory errors with this solution.


Finally, a note about your misconception in your question:

I had figured the joins would be treated as an interruption point...

join, or any of the boost::thread interruption functions, for that matter, is only treated as an interruption point for the thread that calls it. Since your main thread is calling join(), the main thread is the thread that experiences the interruption point, not the thread that it is trying to join. E.g. if you call thread1.interrupt() in some thread and then thread1 calls thread2.join(), then thread1 is the one that gets interrupted.

Upvotes: 1

Ami Tavory
Ami Tavory

Reputation: 76381

In general, there is no portable way for one thread to terminate another, without cooperation from the thread being terminated. This question comes up once in a while, it seems (see here and here - although your question is not an exact duplicate).

Barring cooperation from the thread being interrupted (which would have to perform seppuku on notification), if you would like the main thread to continue after the first of the threads has terminated, you could make a condition that each of the child threads fires when it ends.

At this point, you could either let the other thread continue running (possibly detaching it), or just terminate everything.

Upvotes: 5

Related Questions