Остановка долгоживущих тем

Предположим, у меня есть поток, который должен периодически выполнять какую-то задачу, но этот период 6 раз каждый час 12 раз в час (каждые 5 минут) я часто видел код, управляющий циклом потока с помощью бежит флаг, который проверяется каждый цикл, как это:

std::atomic<bool> is_running;

void start()
{
is_running.store(true);
std::thread { thread_function }.detach();
}

void stop()
{
is_running.store(false);
}

void thread_function()
{
using namespace std::literals;
while (is_running.load())
{
// do some task...
std::this_thread::sleep_for(5min);
}
}

Но если stop() функция вызывается, скажем, через 1 миллисекунду start() поток будет жить в течение 299999 дополнительных миллисекунд, пока не проснется, не проверит флаг и не умрет.

Правильно ли мое понимание? Как избежать сохранения (но спящего) потока, который должен был закончиться? Мой лучший подход до сих пор заключается в следующем:

void thread_function()
{
using namespace std::literals;
while (is_running.load())
{
// do some task...
for (unsigned int b = 0u, e = 1500u; is_running.load() && (b != e); ++b)
{
// 1500 * 200 = 300000ms = 5min
std::this_thread::sleep_for(200ms);
}
}
}

Есть ли менее грязный и более простой способ добиться этого?

18

Решение

Используйте условную переменную. Вы ждете переменную условия или же 5 минут прохождения Не забудьте проверить наличие ложных пробуждений.

cppreference

Я не могу найти хороший пост переполнения стека о том, как использовать переменную условия в течение одной или двух минут поиска в Google. Сложная часть понимает, что wait не может проснуться ни через 5 минут, ни через сигнал. Самый простой способ справиться с этим — использовать методы ожидания с лямбдой, которая дважды проверяет, что пробуждение было «хорошим».

Вот это некоторый пример кода в cppreference, который использует wait_until с лямбдой. (wait_for с лямбда эквивалентно wait_until с лямбдой). Я немного изменил это.

Вот версия:

struct timer_killer {
// returns false if killed:
template<class R, class P>
bool wait_for( std::chrono::duration<R,P> const& time ) const {
std::unique_lock<std::mutex> lock(m);
return !cv.wait_for(lock, time, [&]{return terminate;});
}
void kill() {
std::unique_lock<std::mutex> lock(m);
terminate=true; // should be modified inside mutex lock
cv.notify_all(); // it is safe, and *sometimes* optimal, to do this outside the lock
}
// I like to explicitly delete/default special member functions:
timer_killer() = default;
timer_killer(timer_killer&&)=delete;
timer_killer(timer_killer const&)=delete;
timer_killer& operator=(timer_killer&&)=delete;
timer_killer& operator=(timer_killer const&)=delete;
private:
mutable std::condition_variable cv;
mutable std::mutex m;
bool terminate = false;
};

живой пример.

Вы создаете timer_killer в общем месте. Клиентские потоки могут wait_for( time ), Если он возвращает false, это означает, что вы были убиты до того, как ожидание было завершено.

Управляющий поток просто вызывает kill() и все делают wait_for получает false вернуть.

Обратите внимание, что существует некоторая конкуренция (блокировка мьютекса), поэтому она не подходит для бесконечных потоков (но мало что из этого). Рассмотрите возможность использования планировщика, если вам нужно иметь неограниченное количество задач, которые выполняются с произвольными задержками, а не с полным потоком на задержанную повторяющуюся задачу — каждый реальный поток превышает мегабайт используемого системного адресного пространства (только для стека) ,

17

Другие решения

Есть два традиционных способа сделать это.

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

По-другому вы могли бы poll на трубе с вашим сном как тайм-аут вместо сна. Затем вы просто записываете байт в канал, и поток просыпается и может выйти.

2

да, с помощью std::mutex , std::lock_guard а также std::conditional_variable :

std::mutex mtx;
std::conditional_variable cv;

void someThreadFunction (){
while(!stopThreadFlag){
std::lock_gurad<std::mutex> lg(mtx);
cv.wait_for(lg,SOME_ITERVAL,!stopThreadFlag || wakeTheThreadVariable);
//the rest here
}
}
-1
По вопросам рекламы [email protected]