Пользовательский std::поток, который заканчивается только по сигналу

#c #multithreading #stdthread

Вопрос:

Я уже задавал этот вопрос в другом сообщении, но он получился неудачным, поэтому я хочу перефразировать его лучше.

Я должен запустить серию потоков, выполняющих разные задачи, которые должны возвращаться только в том случае, если был отправлен сигнал выхода, в противном случае (если они вызывают исключения или что-то еще), они просто перезапускают свой код с самого начала.

Чтобы прояснить мое намерение, вот некоторый код:

 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::exceptionamp; e)
            {
                std::cout << e.what() << std::endl;
            }
        }

        return;
    }

};
 

Чего я хочу добиться, так это использовать этот класс так, как это было обычным std::thread, передавая функцию и ее аргументы при ее инициализации, но затем я хочу, чтобы внутренний std::thread запускал функцию «выполнить», и только внутри блока try я хочу, чтобы онзапустите поведение, переданное в конструкторе.

Как я мог этого добиться? Заранее спасибо.

РЕДАКТИРОВАТЬ: я нашел решение, но я могу работать только на c 17 (из-за шаблона на lambda), и, на мой взгляд, это не так элегантно.

 template<typename Lambda>
class thread_wrapper
{
 public:

    explicit thread_wrapper(Lambdaamp;amp; lambda) : lambda_{std::move(lambda)}, th_(amp;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::exceptionamp; e)
            {
                std::cout << e.what() << std::endl;
            }
        }

        return;
    }

};
 

И вот пример основного:

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

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

Что вы думаете?

Ответ №1:

Я бы сказал, что решение, которое вы нашли, в порядке. Возможно, вы захотите избежать того thread_wrapper , чтобы сам по себе был шаблонным классом, и только шаблон конструктора:

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

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

(Я не пытался скомпилировать это — следует ожидать небольших синтаксических ошибок и т. Д. Это больше для демонстрации концепции)

Важно: если вы вызовете signal_exit , это не прервет выполнение lambda_ . Он завершится только после возврата / выброса лямбда-выражения.

Две небольшие вещи, которые следует учитывать при именовании:

  • thread_wrapper не очень подходящее имя. Он ничего не говорит нам о цели или о том, что он делает иначе, чем обычный поток. Может robust_thread быть (для обозначения автоматического восстановления исключения) или что-то в этом роде.
  • Метод signal_exit может быть просто назван exit . Нет причин делать интерфейс этого класса специфичным для сигналов. Вы можете использовать этот класс для любого потока, который должен автоматически перезапускаться, пока ему не будет предложено остановиться какой-либо другой частью кода.

Редактировать: еще одна вещь, которую я забыл, exit_requested_ должна быть либо атомарной, либо защищена мьютексом для защиты от неопределенного поведения. Я бы посоветовал std::atomic<bool> , этого должно быть достаточно в вашем случае.

Ответ №2:

Для этого я бы использовал std::async и конструкцию переменной условия. Я заключил всю логику переменных условий в один класс, чтобы ее можно было легко использовать повторно. Дополнительная информация о переменных условий здесь: https://www.modernescpp.com/index.php/c-core-guidelines-be-aware-of-the-traps-of-condition-variables Не стесняйтесь запрашивать дополнительную информацию, если она вам нужна.

 #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::durationamp; 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 detectedn";

        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, [amp;foo, amp;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 signaln";
    stop_signal.set();
    std::cout << "stop signal setn";

    // synchronize with stopping of the asynchronous process.
    ft.get();
    std::cout << "async process stoppedn";
}