4

std::thread class is inherently exception-unsafe since its destructor calls std::terminate.

std::thread t( function );
// do some work
// (might throw!)
t.join();

You could, of course, put everything in between construction and join() in a try-catch block, but this can get tedious and error-prone if you know you want to join or detach no matter what happens.

So I was thinking how would one go about writing the simplest possible wrappers around it, but that would also support other hypothetical types of threads. For instance, boost::thread or something completely different, as long as it had joinable(), join() and detach() methods. Here's how far I've got:

// handles threads safely
// Acts the same as the underlying thread type, except during destruction.
// If joinable, will call join (and block!) during destruction.
// Keep in mind that any exception handling will get delayed because of that;
// it needs to wait for the thread to finish its work first.
template <class UNDERLYING_THREAD = std::thread>
class scoped_thread: public UNDERLYING_THREAD
{
public:
    typedef UNDERLYING_THREAD thread_type;

    using thread_type::thread_type;

    scoped_thread()
            : thread_type() {}

    scoped_thread( scoped_thread && other )
            : thread_type( std::move( other ) ) {}

    scoped_thread & operator = ( scoped_thread && other )
    {
        thread_type & ref = *this;
        ref = std::move( other );
        return *this;
    }

    ~scoped_thread()
    {
        if( thread_type::joinable() )
            thread_type::join();
    }
};

// handles autonomous threads safely
// Acts the same as the underlying thread type, except during destruction.
// If joinable, will call detach during destruction.
// Make sure it doesn't use any scoped resources since the thread can remain
// running after they go out of scope!
template <class UNDERLYING_THREAD = std::thread>
class free_thread
{
    // same except it calls detach();
}

This seems to work, but I'm wondering if there is a way to avoid manually defining the constructors and the move assignment operator. Probably the biggest issue I noticed is that compilation will fail if you supply a class with deleted move constructor as a template argument.

Do you have any suggestions about how to possibly avoid this? Or are there other, bigger issues with this approach?

9
  • A detach() is a informal way of wasting space in memory and causing hell to pay. Commented Oct 23, 2015 at 16:23
  • If you call detach() you're basically leaving your program to run amuck without chance of stopping it. It's only used for daemons. Which is what a detached thead is called. Commented Oct 23, 2015 at 16:27
  • All threads die when a program exits. All, except detached ones. And sincethey don't have ids, you'rwnot getting them back. They're anon anod doing whatever you told them to do. One infinite loop from emergency machine shutdown. Commented Oct 23, 2015 at 16:30
  • BUT if, you had to detach, and you wanted something to happen when you press a key you would add a mutex to the detached thread and hold std::cin. But as you can see, that'll keep cin stuck to the thread. Therefore it's pointless. Because you couldn't type elsewhere. There is only reasons like listening to ports and such, on servers. Also, machine engineers might use them for a small subset of things. Commented Oct 23, 2015 at 16:43
  • @DavidPulse Don't all threads belong to a process that created them and share its virtual memory? Detaching from a thread doesn't change that, does it? It just informs the OS that you're giving up your reference to it and that it can reclaim resources when the thread exits. So when the process terminates, the detached threads terminate as well. Right? Commented Oct 26, 2015 at 11:05

1 Answer 1

6

If you want proper exception handling with asynchronous tasks, maybe you should use std::future rather than std::thread. Instead of using join(), you'd use get() on the future, and if the future threw an exception, then get() will result in the same exception.

A simple example:

#include <future>
#include <iostream>

int my_future_task(int my_arg) {
    throw std::runtime_error("BAD STUFF!");
    return my_arg;
}

int main(int argc, char* argv[]) {
    auto my_future = std::async(my_future_task, 42);
    try {
        my_future.get();
    }
    catch(std::exception &e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }
    return 0;
}

See also:

Sign up to request clarification or add additional context in comments.

2 Comments

A trivial example of std::async use might help the OP.
@Yakk - Good point. I got distracted before I really flushed this out—and frankly, I was expecting someone else to post a better answer. Anyway, I've added a simple example.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.