#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";
}