c++ - sincronizacion - Detener hilos de larga duración
hilos en java pdf (3)
Supongamos que tengo un hilo que debería realizar alguna tarea periódicamente pero este período es 6 veces cada hora 12 veces cada hora (cada 5 minutos), a menudo he visto un código que controla el bucle de subprocesos con un indicador is_running que se verifica en cada bucle, como este
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);
}
}
Pero si se llama a la función stop()
, digamos, 1 milisegundo después del start()
el hilo estará vivo durante 299999 milisegundos adicionales hasta que se despierte, compruebe la bandera y muera.
¿Mi entendimiento es correcto? ¿Cómo evitar mantener vivo (pero durmiendo) un hilo que debería haberse terminado? Mi mejor enfoque hasta ahora es el siguiente:
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);
}
}
}
¿Hay una manera menos sucia y más directa de lograr esto?
Hay dos formas tradicionales de hacerlo.
Podría usar una espera temporizada en una variable de condición, y hacer que el otro hilo le indique a su hilo periódico que se despierte y muera cuando sea el momento.
Alternativamente, usted podría poll
en una tubería con su sueño como tiempo de espera en lugar de dormir. Luego simplemente escribe un byte en la tubería y el hilo se despierta y puede salir.
Sí, por medio de std::mutex
, std::lock_guard
y 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
}
}
Utilice una variable de condición. Se espera en la variable de condición o 5 minutos pasando. Recuerde comprobar si hay activaciones espurias.
en.cppreference.com/w/cpp/thread/condition_variable
No puedo encontrar una buena publicación de desbordamiento de pila sobre cómo usar una variable de condición en un minuto o dos de búsqueda en Google. La parte difícil es darse cuenta de que la wait
puede despertarse sin que transcurran 5 minutos, ni se envía una señal. La forma más limpia de manejar esto es usar los métodos de espera con un lambda que verifique que la activación haya sido "buena".
here hay un código de ejemplo en cppreference que usa wait_until
con un lambda. ( wait_for
con un lambda es equivalente a wait_until
con un lambda). Lo modifiqué un poco.
Aquí hay una versión:
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;
};
Usted crea un timer_killer
en un lugar compartido. Los hilos de los clientes pueden wait_for( time )
. Si devuelve falso, significa que fue asesinado antes de que se completara su espera.
El hilo controlador simplemente llama a kill()
y todos los que hacen un wait_for
obtienen un retorno false
.
Tenga en cuenta que existe cierta contención (bloqueo del mutex), por lo que esto no es adecuado para subprocesos infinitos (pero pocas cosas lo son). Considere usar un planificador si necesita tener un número ilimitado de tareas que se ejecuten con retrasos arbitrarios en lugar de un subproceso completo por tarea repetida retrasada: cada subproceso real es más de un megabyte de espacio de direcciones del sistema utilizado (solo para la pila) .