Home > Net >  Custom std::thread that ends only on signal
Custom std::thread that ends only on signal

Time:10-24

I already asked this question in another post, but it came out poorly, so I want to rephrase it better.

I have to start a series of threads doing different tasks, that only have to return if an exit signal was sent, otherwise (if they incur in exceptions or anything else) they just restart their code from beginning.

To make my intent clear, here's some code:

class thread_wrapper
{
 public:
    template<typename _Callable, typename... _Args>
    thread_wrapper();

    void signal_exit() {exit_requested_ = true;}

    void join() {th_.join();}


 private:
    std::thread th_;

    bool exit_requested_{false};

    void execute()
    {
        while(!exit_requested_)
        {
            try
            {
                // Do thread processing
            }
            catch (const std::exception& e)
            {
                std::cout << e.what() << std::endl;
            }
        }

        return;
    }

};

What I want to achieve, is to use this class as it was a normal std::thread, passing a function and its arguments when it is initialized, but then I want the inner std::thread to run the "execute" function, and only inside the try block I want it to run the behaviour passed in constructor.

How could I achieve this? Thanks in advance.

EDIT: I found a solution, but I am able to run only in c 17 (because of the template on lambda), and it is not really that elegant in my opinion.

template<typename Lambda>
class thread_wrapper
{
 public:

    explicit thread_wrapper(Lambda&& lambda) : lambda_{std::move(lambda)}, th_(&thread_wrapper::execute, this){};

    void signal_exit() {exit_requested_ = true;}

    void join() {th_.join();}


 private:
    std::thread th_;

    bool exit_requested_{false};

    Lambda lambda_;

    void execute()
    {
        while(!exit_requested_)
        {
            try
            {
                lambda_();
            }
            catch (const std::exception& e)
            {
                std::cout << e.what() << std::endl;
            }
        }

        return;
    }

};

And here is a sample main:

class Foo
{
 public:
    void say_hello() { std::cout << "Hello!" << std::endl;}
};

int main()
{
    Foo foo;
    thread_wrapper th([&foo](){foo.say_hello(); std::this_thread::sleep_for(2s);});
    std::this_thread::sleep_for(10s);
    th.signal_exit();
    th.join();
}

What do you think?

CodePudding user response:

I'd say the solution you found is fine. You might want to avoid the thread_wrapper itself being a templated class and only template the constructor:

// no template
class thread_wrapper {
public:
    template<typename Lambda, typename... Args>
    explicit thread_wrapper(Lambda lambda, Args&&... args) {
        :lambda_(std::bind(lambda, std::forward<Args>(args)...))
    }

    // ...
private:
    std::function<void()> lambda_;
    // ...
};

(I didn't try to compile this - small syntax errors etc are to be expected. It's more to show the concept)

Important: if you do call signal_exit, it will not abort the execution of lambda_. It will only exit once the lambda has returned/thrown.

Two little naming things to consider:

  • thread_wrapper is not a great name. It doesn't tell us anything about the purpose, or what it does different than a regular thread. Maybe robust_thread (to signify the automatic exception recovery) or something.
  • The method signal_exit could just be named exit. There is no reason to make the interface of this class specific to signals. You could use this class for any thread that should auto-restart until it is told to stop by some other part of the code.

Edit: One more thing I forgot, exit_requested_ must be either atomic or protected by a mutex to protect from undefined behavior. I'd suggest an std::atomic<bool>, that should be enough in your case.

CodePudding user response:

I would use std::async and a condition variable construction for this. I wrapped all the condition variable logic in one class so it can easily be reused. More info on condition variables here : https://www.modernescpp.com/index.php/c-core-guidelines-be-aware-of-the-traps-of-condition-variables Don't hesitate to ask for more information if you need it.

#include <chrono>
#include <future>
#include <condition_variable>
#include <mutex>
#include <iostream>
#include <thread>

//-----------------------------------------------------------------------------
// synchronization signal between two threads.
// by using a condition variable the waiting thread
// can even react with the "sleep" time of your example

class signal_t
{
public:
    void set()
    {
        std::unique_lock<std::mutex> lock{m_mtx};
        m_signalled = true;
        // notify waiting threads that something worth waking up for has happened
        m_cv.notify_all();
    }

    bool wait_for(const std::chrono::steady_clock::duration& duration)
    {
        std::unique_lock<std::mutex> lock{ m_mtx };

        // condition variable wait is better then using sleep
        // it can detect signal almost immediately
        m_cv.wait_for(lock, duration, [this] 
        { 
                return m_signalled; 
        }); 

        if ( m_signalled ) std::cout << "signal set detected\n";

        return m_signalled;
    }

private:
    std::mutex m_mtx;
    std::condition_variable m_cv;
    bool m_signalled = false;
};

//-----------------------------------------------------------------------------

class Foo
{
public:
    void say_hello() { std::cout << "Hello!" << std::endl; }
};

//-----------------------------------------------------------------------------

int main()
{
    Foo foo;
    signal_t stop_signal;

    // no need to create a threadwrapper object
    // all the logic fits within the lambda
    // also std::async is a better abstraction then
    // using std::thread. Through the future
    // information on the asynchronous process can
    // be fed back into the calling thread.
    auto ft = std::async(std::launch::async, [&foo, &stop_signal]
    {
        while (!stop_signal.wait_for(std::chrono::seconds(2)))
        {
            foo.say_hello();
        }
    });

    std::this_thread::sleep_for(std::chrono::seconds(10));
    std::cout << "setting stop signal\n";
    stop_signal.set();
    std::cout << "stop signal set\n";

    // synchronize with stopping of the asynchronous process.
    ft.get();
    std::cout << "async process stopped\n";
}
  • Related