2009-06-18 11 views
9

Estoy ejecutando la función Foo desde la biblioteca de otra persona en una aplicación de subproceso único actualmente. La mayoría de las veces, hago una llamada a Foo y es realmente rápido, algunas veces hago una llamada a Foo y me lleva una eternidad. No soy un hombre paciente, si Foo va a durar para siempre, quiero detener la ejecución de Foo y no llamarlo con esos argumentos.¿Puedo crear un hilo de temporizador de watchdog de software en C++ usando Boost Signals2 y Threads?

¿Cuál es la mejor manera de llamar a Foo de forma controlada (mi entorno actual es POSIX/C++) de modo que pueda detener la ejecución después de un cierto número de segundos. Siento que lo correcto es crear un segundo hilo para llamar a Foo, mientras que en mi hilo principal creo una función de temporizador que eventualmente indicará el segundo hilo si se queda sin tiempo.

¿Hay algún otro modelo (y solución) más adecuado? Si no, ¿la biblioteca Signals2 de Boost y los Threads funcionarían?

Respuesta

10

Puede llamar a Foo en un segundo hilo con un tiempo de espera excedido. Por ejemplo:

#include <boost/date_time.hpp> 
#include <boost/thread/thread.hpp> 

boost::posix_time::time_duration timeout = boost::posix_time::milliseconds(500); 
boost::thread thrd(&Foo); 

if (thrd.timed_join(timeout)) 
{ 
    //finished 
} 
else 
{ 
    //Not finished; 
} 
+1

Solo para aclarar, 'timed_join' * no * detener el' Foo() 'ejecución del hilo si se alcanza el tiempo de espera, como pensaba Aron había pedido para. En cambio, la persona que llama simplemente sabrá que el subproceso 'Foo()' aún se estaba ejecutando cuando se alcanzó el tiempo de espera. – pilcrow

2

También puede establecer una alarma justo antes de llamar a esa función y capturar SIGALRM.

6

Puede utilizar la clase siguiente:

class timer 
{ 
    typedef boost::signals2::signal<void()> timeout_slot; 
public: 
    typedef timeout_slot::slot_type timeout_slot_t; 

public: 
    timer() : _interval(0), _is_active(false) {}; 
    timer(int interval) : _interval(interval), _is_active(false) {}; 
    virtual ~timer() { stop(); }; 

    inline boost::signals2::connection connect(const timeout_slot_t& subscriber) { return _signalTimeout.connect(subscriber); }; 

    void start() 
    { 
     boost::lock_guard<boost::mutex> lock(_guard); 

     if (is_active()) 
      return; // Already executed. 
     if (_interval <= 0) 
      return; 

     _timer_thread.interrupt(); 
     _timer_thread.join(); 

     timer_worker job; 
     _timer_thread = boost::thread(job, this); 

     _is_active = true; 
    }; 

    void stop() 
    { 
     boost::lock_guard<boost::mutex> lock(_guard); 

     if (!is_active()) 
      return; // Already executed. 

     _timer_thread.interrupt(); 
     _timer_thread.join(); 

     _is_active = false; 
    }; 

    inline bool is_active() const { return _is_active; }; 

    inline int get_interval() const { return _interval; }; 

    void set_interval(const int msec) 
    { 
     if (msec <= 0 || _interval == msec) 
      return; 

     boost::lock_guard<boost::mutex> lock(_guard); 
     // Keep timer activity status. 
     bool was_active = is_active(); 

     if (was_active) 
      stop(); 
     // Initialize timer with new interval. 
     _interval = msec; 

     if (was_active) 
      start(); 
    }; 

protected: 
    friend struct timer_worker; 
    // The timer worker thread. 
    struct timer_worker 
    { 
     void operator()(timer* t) 
     { 
      boost::posix_time::milliseconds duration(t->get_interval()); 

      try 
      { 
       while (1) 
       { 
        boost::this_thread::sleep<boost::posix_time::milliseconds>(duration); 
        { 
         boost::this_thread::disable_interruption di; 
         { 
          t->_signalTimeout(); 
         } 
        } 
       } 
      } 
      catch (boost::thread_interrupted const&) 
      { 
       // Handle the thread interruption exception. 
       // This exception raises on boots::this_thread::interrupt. 
      } 
     }; 
    }; 

protected: 
    int    _interval; 
    bool   _is_active; 

    boost::mutex _guard; 
    boost::thread _timer_thread; 

    // Signal slots 
    timeout_slot _signalTimeout; 
}; 

Un ejemplo de uso:

void _test_timer_handler() 
{ 
    std::cout << "_test_timer_handler\n"; 
} 

BOOST_AUTO_TEST_CASE(test_timer) 
{ 
    emtorrus::timer timer; 

    BOOST_CHECK(!timer.is_active()); 
    BOOST_CHECK(timer.get_interval() == 0); 

    timer.set_interval(1000); 
    timer.connect(_test_timer_handler); 

    timer.start(); 

    BOOST_CHECK(timer.is_active()); 

    std::cout << "timer test started\n"; 

    boost::this_thread::sleep<boost::posix_time::milliseconds>(boost::posix_time::milliseconds(5500)); 

    timer.stop(); 

    BOOST_CHECK(!timer.is_active()); 
    BOOST_CHECK(_test_timer_count == 5); 
} 
1

Vlad, excelente post! Tu código compilado y funciona maravillosamente. Implementé un temporizador de vigilancia de software con él. Hice algunas modificaciones:

  • Para evitar la caída del puntero, almacene la señal en boost :: shared_ptr y páselo al trabajador de hilos en lugar de un puntero débil a la clase del temporizador. Esto elimina la necesidad de que el trabajador de hilos sea una estructura amiga y garantiza que la señal esté en la memoria.
  • Agregue el parámetro _is_periodic para permitir que la persona que llama seleccione si el hilo del trabajador es periódico o si termina después de la caducidad.
  • Tienda _is_active, _interval y _is_periodic in boost :: atomic para permitir el acceso seguro de subprocesos.
  • Reduzca el alcance del bloqueo de exclusión mutua.
  • Agregue el método de reinicio() para "patear" el temporizador, evitando que emita la señal de vencimiento.

Con estos cambios aplicados:

#include <atomic> 
#include <boost/signals2.hpp> 
#include <boost/thread.hpp> 

class IntervalThread 
{ 
    using interval_signal = boost::signals2::signal<void(void)>; 

public: 
    using interval_slot_t = interval_signal::slot_type; 

    IntervalThread(const int interval_ms = 60) 
     : _interval_ms(interval_ms), 
     _is_active(false), 
     _is_periodic(false), 
     _signal_expired(new interval_signal()) {}; 

    inline ~IntervalThread(void) { stop(); }; 

    boost::signals2::connection connect(const interval_slot_t &subscriber) 
    { 
     // thread-safe: signals2 obtains a mutex on connect() 
     return _signal_expired->connect(subscriber); 
    }; 

    void start(void) 
    { 
     if (is_active()) 
      return; // Already executed. 
     if (get_interval_ms() <= 0) 
      return; 

     boost::lock_guard<boost::mutex> lock(_timer_thread_guard); 
     _timer_thread.interrupt(); 
     _timer_thread.join(); 

     _timer_thread = boost::thread(timer_worker(), 
       static_cast<int>(get_interval_ms()), 
       static_cast<bool>(is_periodic()), 
       _signal_expired); 
     _is_active = true; 
    }; 

    void reset(void) 
    { 
     if (is_active()) 
      stop(); 
     start(); 
    } 

    void stop(void) 
    { 
     if (!is_active()) 
      return; // Already executed. 

     boost::lock_guard<boost::mutex> lock(_timer_thread_guard); 
     _timer_thread.interrupt(); 
     _timer_thread.join(); 
     _is_active = false; 
    }; 

    inline bool is_active(void) const { return _is_active; }; 

    inline int get_interval_ms(void) const { return _interval_ms; }; 

    void set_interval_ms(const int interval_ms) 
    { 
     if (interval_ms <= 0 || get_interval_ms() == interval_ms) 
      return; 

     // Cache timer activity state. 
     const bool was_active = is_active(); 
     // Initialize timer with new interval. 
     if (was_active) 
      stop(); 
     _interval_ms = interval_ms; 
     if (was_active) 
      start(); 
    }; 

    inline bool is_periodic(void) const { return _is_periodic; } 
    inline void set_periodic(const bool is_periodic = true) { _is_periodic = is_periodic; } 

private: 
    // The timer worker for the interval thread. 
    struct timer_worker { 
     void operator()(const int interval_ms, const bool is_periodic, boost::shared_ptr<interval_signal> signal_expired) 
     { 
      boost::posix_time::milliseconds duration(interval_ms); 
      try { 
       do { 
        boost::this_thread::sleep<boost::posix_time::milliseconds>(duration); 
        { 
         boost::this_thread::disable_interruption di; 
         signal_expired->operator()(); 
        } 
       } while (is_periodic); 
      } catch (const boost::thread_interrupted &) { 
       // IntervalThread start(), stop() and reset() throws boost::this_thread::interrupt, 
       // which is expected since this thread is interrupted. No action neccessary. 
      } 
     }; 
    }; 

    std::atomic<int> _interval_ms; // Interval, in ms 
    std::atomic<bool> _is_active; // Is the timed interval active? 
    std::atomic<bool> _is_periodic; // Is the timer periodic? 

    boost::mutex _timer_thread_guard; 
    boost::thread _timer_thread; 

    // The signal to call on interval expiration. 
    boost::shared_ptr<interval_signal> _signal_expired; 
}; 
Cuestiones relacionadas