2010-11-25 22 views
1

Je voudrais obtenir quelques commentaires concernant la classe IService listée ci-dessous. D'après ce que je sais, ce type de classe est lié au modèle "objet-actif". Veuillez excuser/corriger si j'utilise une terminologie incorrecte de manière incorrecte. Fondamentalement, l'idée est que les classes utilisant cette classe d'objets actifs doivent fournir une méthode start et une méthode stop qui contrôlent une boucle d'événement. Cette boucle d'événement peut être implémentée avec une boucle while ou avec asio boostquestions relatives à la conception d'objet actif liées au threading (C++ boost)

Cette classe est chargée de démarrer un nouveau thread de manière non bloquante afin que les événements puissent être traités dans/par le nouveau thread. Il doit également gérer tout le code lié au nettoyage. J'ai d'abord essayé une approche OO dans laquelle les sous-classes étaient responsables des méthodes de contrôle de la boucle d'événements mais le nettoyage était désordonné: dans le destructeur appelant la méthode stop, un appel de fonction virtuelle pure était généré dans les cas où la classe appelante méthode d'arrêt. La solution templated semble être beaucoup plus propre:

template <typename T> 
class IService : private boost::noncopyable 
{ 
    typedef boost::shared_ptr<boost::thread> thread_ptr; 
public: 

    IService() 
    { 
    } 

    ~IService() 
    { 
    /// try stop the service in case it's running 
    stop(); 
    } 

    void start() 
    { 
    boost::mutex::scoped_lock lock(m_threadMutex); 

    if (m_pServiceThread && m_pServiceThread->joinable()) 
    { 
     // already running 
     return; 
    } 

    m_pServiceThread = thread_ptr(new boost::thread(boost::bind(&IService::main, this))); 

    // need to wait for thread to start: else if destructor is called before thread has started 

    // Wait for condition to be signaled and then 
    // try timed wait since the application could deadlock if the thread never starts? 
    //if (m_startCondition.timed_wait(m_threadMutex, boost::posix_time::milliseconds(getServiceTimeoutMs()))) 
    //{ 
    //} 
    m_startCondition.wait(m_threadMutex); 

    // notify main to continue: it's blocked on the same condition var 
    m_startCondition.notify_one(); 
    } 

    void stop() 
    { 
    // trigger the stopping of the event loop 
    m_serviceObject.stop(); 

    if (m_pServiceThread) 
    { 
     if (m_pServiceThread->joinable()) 
     { 
     m_pServiceThread->join(); 
     } 
     // the service is stopped so we can reset the thread 
     m_pServiceThread.reset(); 
    } 
    } 

private: 
    /// entry point of thread 
    void main() 
    { 
    boost::mutex::scoped_lock lock(m_threadMutex); 
    // notify main thread that it can continue 
    m_startCondition.notify_one(); 

    // Try Dummy wait to allow 1st thread to resume??? 
    m_startCondition.wait(m_threadMutex); 

    // call template implementation of event loop 
    m_serviceObject.start(); 
    } 

    /// Service thread 
    thread_ptr m_pServiceThread; 
    /// Thread mutex 
    mutable boost::mutex m_threadMutex; 
    /// Condition for signaling start of thread 
    boost::condition m_startCondition; 

    /// T must satisfy the implicit service interface and provide a start and a stop method 
    T m_serviceObject; 
}; 

La classe pourrait être utilisé comme suit:

class TestObject3 
{ 
public: 
    TestObject3() 
     :m_work(m_ioService), 
     m_timer(m_ioService, boost::posix_time::milliseconds(200)) 
    { 
     m_timer.async_wait(boost::bind(&TestObject3::doWork, this, boost::asio::placeholders::error)); 
    } 

    void start() 
    { 
     // simple event loop 
     m_ioService.run(); 
    } 

    void stop() 
    { 
     // signal end of event loop 
     m_ioService.stop(); 
    } 

    void doWork(const boost::system::error_code& e) 
    { 
     // Do some work here 
     if (e != boost::asio::error::operation_aborted) 
     { 
     m_timer.expires_from_now(boost::posix_time::milliseconds(200)); 
     m_timer.async_wait(boost::bind(&TestObject3::doWork, this, boost::asio::placeholders::error)); 
     } 
    } 

private: 
    boost::asio::io_service m_ioService; 
    boost::asio::io_service::work m_work; 
    boost::asio::deadline_timer m_timer; 
}; 

maintenant à mes questions spécifiques:

1) L'utilisation du boost variable d'état correcte? Cela me semble un peu un hack: je voulais attendre que le thread soit lancé donc j'ai attendu sur la variable de condition. Puis, une fois que le nouveau thread a été lancé dans la méthode principale, j'attends à nouveau sur la même variable de condition pour permettre au thread initial de continuer. Ensuite, une fois que la méthode de démarrage du thread initial est terminée, le nouveau thread peut continuer. Est-ce correct?

2) Y a-t-il des cas dans lesquels le thread ne serait pas lancé avec succès par le système d'exploitation? Je me souviens d'avoir lu quelque part que cela pouvait se produire. Si cela est possible, je devrais plutôt faire une attente temporisée sur la variable de condition (comme cela est commenté dans la méthode start)? 3) Je suis conscient que de la classe modèle ne pourrait pas implémenter la méthode d'arrêt "correctement" ie si la boucle d'événement ne s'arrête pas, le code bloquera sur les jointures (soit dans le stop ou dans le destructeur) Je ne vois aucun moyen de contourner cela. Je suppose que c'est à l'utilisateur de la classe de s'assurer que la méthode start et stop est implémentée correctement?

4) J'apprécierais d'autres erreurs de conception, améliorations, etc.

Merci!

Répondre

0

Enfin installés sur les points suivants:

1) Après utilisation beaucoup de tests de variable de condition semble bien

2) Cette question n'a pas recadrée (encore)

3) La classe templated la mise en œuvre doit satisfaire aux exigences, les tests unitaires sont utilisés pour test correct

4) améliorations

  • Ajouté rejoindre avec serrure
  • Attraper exceptions en fil donné naissance et rethrowing dans thread principal pour éviter les accidents et de ne pas d'information lâche d'exception
  • En utilisant boost :: système :: error_code pour communiquer les codes d'erreur de retour à l'appelant
  • objet de mise en œuvre est mis en mesure

code:

template <typename T> 
class IService : private boost::noncopyable 
{ 
    typedef boost::shared_ptr<boost::thread> thread_ptr; 
    typedef T ServiceImpl; 
public: 
    typedef boost::shared_ptr<IService<T> > ptr; 

    IService() 
    :m_pServiceObject(&m_serviceObject) 
    { 
    } 

    ~IService() 
    { 
    /// try stop the service in case it's running 
    if (m_pServiceThread && m_pServiceThread->joinable()) 
    { 
     stop(); 
    } 
    } 

    static ptr create() 
    { 
    return boost::make_shared<IService<T> >(); 
    } 

    /// Accessor to service implementation. The handle can be used to configure the implementation object 
    ServiceImpl& get() { return m_serviceObject; } 
    /// Mutator to service implementation. The handle can be used to configure the implementation object 
    void set(ServiceImpl rServiceImpl) 
    { 
    // the implementation object cannot be modified once the thread has been created 
    assert(m_pServiceThread == 0); 
    m_serviceObject = rServiceImpl; 
    m_pServiceObject = &m_serviceObject; 
    } 

    void set(ServiceImpl* pServiceImpl) 
    { 
    // the implementation object cannot be modified once the thread has been created 
    assert(m_pServiceThread == 0); 

    // make sure service object is valid 
    if (pServiceImpl) 
     m_pServiceObject = pServiceImpl; 
    } 

    /// if the service implementation reports an error from the start or stop method call, it can be accessed via this method 
    /// NB: only the last error can be accessed 
    boost::system::error_code getServiceErrorCode() const { return m_ecService; } 

    /// The join method allows the caller to block until thread completion 
    void join() 
    { 
    // protect this method from being called twice (e.g. by user and by stop) 
    boost::mutex::scoped_lock lock(m_joinMutex); 
    if (m_pServiceThread && m_pServiceThread->joinable()) 
    { 
     m_pServiceThread->join(); 
     m_pServiceThread.reset(); 
    } 
    } 

    /// This method launches the non-blocking service 
    boost::system::error_code start() 
    { 
    boost::mutex::scoped_lock lock(m_threadMutex); 

    if (m_pServiceThread && m_pServiceThread->joinable()) 
    { 
     // already running 
     return boost::system::error_code(SHARED_INVALID_STATE, shared_category); 
    } 

    m_pServiceThread = thread_ptr(new boost::thread(boost::bind(&IService2::main, this))); 
    // Wait for condition to be signaled 
    m_startCondition.wait(m_threadMutex); 

    // notify main to continue: it's blocked on the same condition var 
    m_startCondition.notify_one(); 
    // No error 
    return boost::system::error_code(); 
    } 

    /// This method stops the non-blocking service 
    boost::system::error_code stop() 
    { 
    // trigger the stopping of the event loop 
    //boost::system::error_code ec = m_serviceObject.stop(); 
    assert(m_pServiceObject); 
    boost::system::error_code ec = m_pServiceObject->stop(); 
    if (ec) 
    { 
     m_ecService = ec; 
     return ec; 
    } 

    // The service implementation can return an error code here for more information 
    // However it is the responsibility of the implementation to stop the service event loop (if running) 
    // Failure to do so, will result in a block 
    // If this occurs in practice, we may consider a timed join? 
    join(); 

    // If exception was thrown in new thread, rethrow it. 
    // Should the template implementation class want to avoid this, it should catch the exception 
    // in its start method and then return and error code instead 
    if(m_exception) 
     boost::rethrow_exception(m_exception); 

    return ec; 
    } 

private: 
    /// runs in it's own thread 
    void main() 
    { 
    try 
    { 
     boost::mutex::scoped_lock lock(m_threadMutex); 
     // notify main thread that it can continue 
     m_startCondition.notify_one(); 
     // Try Dummy wait to allow 1st thread to resume 
     m_startCondition.wait(m_threadMutex); 

     // call implementation of event loop 
     // This will block 
     // In scenarios where the service fails to start, the implementation can return an error code 
     m_ecService = m_pServiceObject->start(); 

     m_exception = boost::exception_ptr(); 
    } 
    catch (...) 
    { 
     m_exception = boost::current_exception(); 
    } 
    } 

    /// Service thread 
    thread_ptr m_pServiceThread; 
    /// Thread mutex 
    mutable boost::mutex m_threadMutex; 
    /// Join mutex 
    mutable boost::mutex m_joinMutex; 
    /// Condition for signaling start of thread 
    boost::condition m_startCondition; 

    /// T must satisfy the implicit service interface and provide a start and a stop method 
    T m_serviceObject; 
    T* m_pServiceObject; 
    // Error code for service implementation errors 
    boost::system::error_code m_ecService; 

    // Exception ptr to transport exception across different threads 
    boost::exception_ptr m_exception; 
}; 

autres commentaires/la critique serait bien sûr la bienvenue.