2009-06-18 7 views
9

Attualmente sto eseguendo la funzione Foo dalla libreria di qualcun altro in un'applicazione a thread singolo. Il più delle volte, faccio una chiamata a Foo ed è molto veloce, altre volte, faccio una chiamata a Foo e ci vuole sempre. Non sono un uomo paziente, se Foo ci vorrà per sempre, voglio fermare l'esecuzione di Foo e non chiamarlo con quegli argomenti.È possibile creare un thread del timer del watchdog del software in C++ utilizzando i segnali Boost2 e Thread?

Qual è il modo migliore per chiamare Foo in modo controllato (il mio ambiente corrente è POSIX/C++) in modo tale da poter interrompere l'esecuzione dopo un certo numero di secondi. Sento che la cosa giusta da fare qui è creare un secondo thread per chiamare Foo, mentre nel mio thread principale creo una funzione timer che alla fine segnalerà il secondo thread se si esaurisce il tempo.

Esiste un altro modello più adatto (e una soluzione)? In caso contrario, la libreria e le discussioni di Boost's Signals2 faranno il trucco?

risposta

10

È possibile chiamare Foo su un secondo thread con un timeout. Per esempio:

#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 per chiarire, 'timed_join' * non * fermare la' Foo() 'esecuzione del thread se viene raggiunto il timeout, come ho pensato Aron aveva chiesto per. Invece, il chiamante saprà semplicemente che il thread 'Foo()' era ancora in esecuzione quando è stato raggiunto il timeout. – pilcrow

2

È inoltre possibile impostare un allarme subito prima di chiamare tale funzione e catturare SIGALRM.

6

È possibile utilizzare la seguente classe:

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 esempio di utilizzo:

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, ottimo post! Il tuo codice è compilato e funziona magnificamente. Ho implementato un timer watchdog software con esso. Ho apportato alcune modifiche:

  • Per evitare il decadimento del puntatore, memorizzare il segnale in boost :: shared_ptr e passare questo al thread worker invece di un puntatore debole alla classe timer. Ciò elimina la necessità per il thread worker di essere una struttura friend e garantisce che il segnale sia in memoria.
  • Aggiungere parametro _is_periodic per consentire al chiamante di selezionare se il thread di lavoro è periodico o se termina dopo la scadenza.
  • Memorizza _is_active, _interval e _is_periodic in boost :: atomic per consentire l'accesso sicuro ai thread.
  • Restringi l'ambito del blocco mutex.
  • Aggiungi il metodo reset() per "calciare" il timer, impedendogli di emettere il segnale di scadenza.

Con queste modifiche applicate:

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