Search code examples
c++multithreadingexception

c++ running multiple threads and return in main function once a thread throws


#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <stdexcept>


class MyClass {
public:

    void run() {
        // do some work
    }
    struct Data {
        double x{0.0};
        int y{0};
    } data;
};

void Thread_function(int i, MyClass myclass, std::exception_ptr &exc, std::mutex &mtx)
{
    try {
        // Modify some of the struct parameters
        auto dataCopy = myclass.data;
        dataCopy.x = i + 1;
        dataCopy.y = (i + 1) * 0.5;
        // run the simulation with changed parameters
        myclass.data = dataCopy;
        myclass.run();
    } 
    catch (const std::exception &exception)
    {
        // lock capturedException
        std::lock_guard<std::mutex> lock(mtx);
        exc = std::current_exception();
    }
}

int main() {

    const int num_threads = 5;
    std::vector<std::thread> threads;
    std::exception_ptr capturedException;
    std::mutex mtx;
    MyClass myClass;

    // create threads
    for (int i = 0; i < num_threads; ++i) 
    {
        threads.emplace_back(
            Thread_function, i, myClass, std::ref(capturedException), std::ref(mtx));
    }

    // wait for all threads to finish
    for (auto& t : threads) 
    {
        t.join();
    }

    // main thread rethrows exception, if any
    if (capturedException)
    {
        throw capturedException;
        return 1;
    }

    return 0;
}

Several threads are created in the main function which do some computations in the runfunction. In the thread function, I used try/catchblock to catch exceptions and std::lock_guard<std::mutex> for synchronization since excis a shared variable among all threads (passed by reference). In the main function, I wait for all threads to join and then rethrow exception from the threads.

However, in main, I want to return 1 once a thread throwed an exception. No need to wait for the other threads then. At the moment, m̀ain waits for all threads to finish although one one of the threads possibly throwed an exception already. How can I fix this? Are there classes in c++ library to handle this smoothly?


Solution

  • Note that I left the previous answer as an example; I've completely rewritten your code, and eliminated your MyClass since I don't know what it does anyway. In this example, the actual code goes into the MyThread::run function.'

    It also gives a few more outputs.

    The important thing is that we no longer wait for exceptions, since threads need to quit anyway. Instead, your run function needs to check the quit-condition frequently enough to suit your taste -- when one of the threads throws, the other threads will quit next time they call the performQuit function.

    performQuit works by throwing an exception so it can be called from anywhere inside your stuff. This exception is caught and discarded, though.

    A lot of changes are just me generally doing things differently than you.

    Note that my example run runs for "thread number seconds", so they would normally quit every 1 second. This gets aborted when #3 throws its exception after 2.5 seconds.

    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <stdexcept>
    #include <list>
    #include <chrono>
    
    /************************************************************************/
    
    class MyThread
    {
    private:
        static inline const std::chrono::time_point startTime=std::chrono::system_clock::now();
    
    private:
        static inline std::mutex mutex;
        static inline std::exception_ptr capturedException;
    
    private:
        class OperationCancelled { };
    
        static void performQuit()
        {
            std::lock_guard<decltype(mutex)> lock(mutex);
            if (capturedException)
            {
                throw OperationCancelled();
            }
        }
    
        static void captureException()
        {
            // first exception "wins"; any other ones are lost since
            // we only have room for one.
    
            std::lock_guard<decltype(mutex)> lock(mutex);
            if (!capturedException)
            {
                capturedException=std::current_exception();
            }
        }
    
    public:
        static void rethrowIf()
        {
            if (capturedException)
            {
                std::rethrow_exception(capturedException);
            }
        }
    
    private:
        std::thread thread;
        const int number;
    
    private:
        void printStatus(std::string_view string)
        {
            auto elapsed=std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()-startTime);
    
            std::lock_guard<decltype(mutex)> lock(mutex);
            std::cout << elapsed.count() << "ms: thread " << number << " " << string << "\n";
        }
    
    private:
        void run()
        {
            // We need to check the quit condition during the processing, so
            // for convenience reasons I'll pretend the actual job has a loop
            // so we can just check on each iteration. If it doesn't, add the
            // check in between work chunks or whatever -- the thread will
            // only quit on a check, so it's up to you how quickly the threads
            // will respond to a quit...
    
            for (int i=0; i<100*number; i++)
            {
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
                performQuit();
    
                if (number==3 && i==250) throw std::runtime_error("boo!");
            }
        }
    
    private:
        // this acts as a wrapper around the "run" function, so we keep
        // the clerical tasks separated from the actual function.
        void body()
        {
            printStatus("launched");
            try
            {
                run();
                printStatus("has completed task");
            }
            catch(const OperationCancelled&)
            {
                printStatus("was aborted");
            }
            catch(...)
            {
                printStatus("got exception");
                captureException();
            }
            printStatus("exit");
        }
    
    public:
        MyThread(int number_)
            : number(number_)
        {
            thread=std::thread([this](){ body(); });
        }
    
        ~MyThread()
        {
            thread.join();
        }
    };
    
    /************************************************************************/
    
    int main() {
    
        const int num_threads = 6;
    
        // create threads and wait for them to finish
        {
            std::list<MyThread> threads;
            for (int i = 0; i < num_threads; ++i)
            {
                threads.emplace_back(i);
            }
        }
    
        try
        {
            MyThread::rethrowIf();
        }
        catch(const std::exception& exception)
        {
            std::cerr << "exception: " << exception.what() << "\n";
            return EXIT_FAILURE;
        }
    
        return EXIT_SUCCESS;
    }
    

    The output:

    stieber@gatekeeper:~ $ g++ Test.cpp && ./a.out
    0ms: thread 0 launched
    0ms: thread 0 has completed task
    0ms: thread 0 exit
    0ms: thread 1 launched
    0ms: thread 2 launched
    0ms: thread 3 launched
    0ms: thread 4 launched
    0ms: thread 5 launched
    1007ms: thread 1 has completed task
    1007ms: thread 1 exit
    2014ms: thread 2 has completed task
    2014ms: thread 2 exit
    2528ms: thread 3 got exception
    2528ms: thread 3 exit
    2538ms: thread 4 was aborted
    2538ms: thread 4 exit
    2538ms: thread 5 was aborted
    2538ms: thread 5 exit
    exception: boo!