2011-07-11 11 views
5

El siguiente ejemplo de código mínimo de un programa más grande envía comandos desde los hilos del cliente a un objeto asio io_service. El objeto io_service (en la clase Ios) se está ejecutando con un subproceso. Cuando se envía el comando, el hilo del cliente espera hasta que el objeto Ios (a través de Cmd :: NotifyFinish()) le notifique que se ha completado.problema de condición de impulso

Este ejemplo parece ejecutarse en Linux Ubuntu 11.04 con impulso 1.46 bien pero en Windows 7 impulso 1.46 asegura.

Sospecho que tiene algo que ver con el bloqueo en Cmd :: NotifyFinish(). Cuando retiro el bloqueo del ámbito anidado para que cuando se llame a waitConditionVariable_.notify_one() en el alcance del bloqueo no se bloquee en Windows 7. Sin embargo, la documentación boost :: thread indica que notify_one() no necesita ser llamado dentro de la cerradura.

El seguimiento de la pila (a continuación) muestra que se está afirmando cuando se llama a notify_one(). Es como si el objeto cmd hubiera desaparecido antes de que se llame a notify ...

¿Cómo puedo hacer que este thread sea seguro y no afirme?

#include <boost/asio.hpp> 
#include <boost/thread/thread.hpp> 
#include <boost/thread/mutex.hpp> 
#include <boost/thread/locks.hpp> 
#include <boost/thread/condition_variable.hpp> 
#include <boost/bind.hpp> 
#include <iostream> 

class Cmd 
{ 
public: 
    Cmd() : cnt_(0), waitPred_(false), waiting_(false) 
    { 
    } 
    virtual ~Cmd() 
    { 
    } 
    void BindInfo(int CmdSeq) 
    { 
     cnt_ = CmdSeq; 
    } 
    void NotifyFinish() 
    { 
     // call by service thread... 
     { 
      boost::mutex::scoped_lock lock(waitMutex_); 
      waitPred_ = true; 
      if (!waiting_) 
      { 
       // don't need to notify as isn't waiting 
       return; 
      } 
     } 
     waitConditionVariable_.notify_one(); 
    } 
    void Wait() 
    { 
     // called by worker threads 
     boost::mutex::scoped_lock lock(waitMutex_); 
     waiting_ = true; 
     while (!waitPred_) 
      waitConditionVariable_.wait(lock); 
    } 
    int cnt_; 
private: 

    boost::mutex waitMutex_; 
    boost::condition_variable waitConditionVariable_; 
    bool waitPred_; 
    bool waiting_; 
}; 


class Ios 
{ 
public: 
    Ios() : timer_(ios_), cnt_(0), thread_(boost::bind(&Ios::Start, this)) 
    { 
    } 
    void Start() 
    { 
     timer_.expires_from_now(boost::posix_time::seconds(5)); 
     timer_.async_wait(boost::bind(&Ios::TimerHandler, this, _1)); 
     ios_.run(); 
    } 
    void RunCmd(Cmd& C) 
    { 
     ios_.post(boost::bind(&Ios::RunCmdAsyn, this, boost::ref(C))); 
    } 

private: 
    void RunCmdAsyn(Cmd& C) 
    { 
     C.BindInfo(cnt_++); 
     C.NotifyFinish(); 
    } 
    void TimerHandler(const boost::system::error_code& Ec) 
    { 
     if (!Ec) 
     { 
      std::cout << cnt_ << "\n"; 
      timer_.expires_from_now(boost::posix_time::seconds(5)); 
      timer_.async_wait(boost::bind(&Ios::TimerHandler, this, _1)); 
     } 
     else 
      exit(0); 
    } 

    boost::asio::io_service ios_; 
    boost::asio::deadline_timer timer_; 
    int cnt_; 
    boost::thread thread_; 
}; 

static Ios ios; 

void ThreadFn() 
{ 
    while (1) 
    { 
     Cmd c; 
     ios.RunCmd(c); 
     c.Wait(); 
     //std::cout << c.cnt_ << "\n"; 
    } 
} 

int main() 
{ 
    std::cout << "Starting\n"; 
    boost::thread_group threads; 
    const int num = 5; 

    for (int i = 0; i < num; i++) 
    { 
     // Worker threads 
     threads.create_thread(ThreadFn); 
    } 
    threads.join_all(); 

} 

seguimiento de la pila

msvcp100d.dll!std::_Debug_message(const wchar_t * message, const wchar_t * file, unsigned int line) Line 15 C++ 
iosthread.exe!std::_Vector_const_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > >::_Compat(const std::_Vector_const_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > > & _Right) Line 238 + 0x17 bytes C++ 
iosthread.exe!std::_Vector_const_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > >::operator==(const std::_Vector_const_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > > & _Right) Line 203 C++ 
iosthread.exe!std::_Vector_const_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > >::operator!=(const std::_Vector_const_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > > & _Right) Line 208 + 0xc bytes C++ 
iosthread.exe!std::_Debug_range2<std::_Vector_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > > >(std::_Vector_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > > _First, std::_Vector_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > > _Last, const wchar_t * _File, unsigned int _Line, std::random_access_iterator_tag __formal) Line 715 + 0xc bytes C++ 
iosthread.exe!std::_Debug_range<std::_Vector_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > > >(std::_Vector_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > > _First, std::_Vector_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > > _Last, const wchar_t * _File, unsigned int _Line) Line 728 + 0x6c bytes C++ 
iosthread.exe!std::find_if<std::_Vector_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > >,bool (__cdecl*)(boost::intrusive_ptr<boost::detail::basic_cv_list_entry> const &)>(std::_Vector_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > > _First, std::_Vector_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > > _Last, bool (const boost::intrusive_ptr<boost::detail::basic_cv_list_entry> &)* _Pred) Line 92 + 0x54 bytes C++ 
iosthread.exe!std::remove_if<std::_Vector_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > >,bool (__cdecl*)(boost::intrusive_ptr<boost::detail::basic_cv_list_entry> const &)>(std::_Vector_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > > _First, std::_Vector_iterator<std::_Vector_val<boost::intrusive_ptr<boost::detail::basic_cv_list_entry>,std::allocator<boost::intrusive_ptr<boost::detail::basic_cv_list_entry> > > > _Last, bool (const boost::intrusive_ptr<boost::detail::basic_cv_list_entry> &)* _Pred) Line 1848 + 0x58 bytes C++ 
iosthread.exe!boost::detail::basic_condition_variable::notify_one() Line 267 + 0xb4 bytes C++ 
iosthread.exe!Cmd::NotifyFinish() Line 41 C++ 
+0

+1 para el reproductor –

Respuesta

5

El problema es que la variable de condición es un miembro del objeto Cmd que se crea por el hilo de cliente y es destruido por ese hilo cliente cuando la espera es terminado.

para que tenga una condición de carrera donde:

  • boost::condition_variable::notify_one() se llama en el 'hilo servicio'
  • que desbloquea el hilo cliente que está esperando en esa condición variable
  • el hilo cliente puede destruir la variable de condición con la que la cadena de servicio todavía está trabajando en su llamada al notify_one.

Así que su observación de que es "como si el objeto cmd ha desaparecido antes de que se notifique que se llama" es más o menos exactamente lo que sucedió, creo. Excepto que el objeto Cmd no desapareció antes de llamar a notify_one(), desapareció mientras notify_one() estaba haciendo su trabajo. Su otra nota que "la documentación boost::thread indica que notify_one() no necesita ser llamado dentro del bloqueo" es verdadera, pero eso no significa que la variable de condición pueda destruirse antes de que notify_one() haya regresado.

que necesita para gestionar el tiempo de vida del objeto Cmd manera que el hilo servicio se realiza utilizando antes de que se destruye - sosteniendo el mutex que está en el objeto Cmd mientras notify_one() se llama es una manera de hacerlo (como' he notado) O puede extraer la variable de condición del objeto Cmd para que su duración sea independiente del objeto Cmd (tal vez shared_ptr<> puede ayudar con eso).

Además, cuenta que yo creo que la waiting_ miembro de la clase Cmd es superflua - puede llamar notify_one() o notify_all() cuando no hay camareros en una variable de estado - que ya está haciendo la comprobación de que para usted (I don' Creo que está dañando cualquier cosa, solo que es una complejidad que no necesita estar en la clase Cmd.

+0

Gracias por su respuesta Michael. ¿No se moverá el bloqueo en NotifyFinish() a la fuerza del alcance externo notify_one() antes de que Wait() haya salido, porque waitConditionVariable_.wait obtiene el bloqueo después de finalizar? Entonces hay dos escenarios que Wait puede salir. O bien cuando Notify obtiene el bloqueo antes de que Wait lo obtenga o cuando Wait obtiene el bloqueo primero y luego se notifica la variable de condición. Estoy de acuerdo en que la memoria de espera parece superflua. – Liam

+0

@Liam: suponiendo que el cambio se ha realizado para que 'NotifyFinish()' contenga el mutex mientras llama a 'notify_one()', luego en su primer escenario donde 'NotifyFinish()' se llama primero, tomará el mutex , establezca 'waitPred_ = true', luego llame a' notify_one() '(que no hará nada). Todo eso sucederá antes de que 'Wait()' tenga la oportunidad de hacer algo, porque si lo intenta, se bloqueará esperando a adquirir el mutex. Una vez que 'NotifyFinish()' libera el mutex, la llamada 'Wait()' podrá continuar; notará que 'waitPred_' es' true' y no molestará en llamar 'condition_variable :: wait()'. –

1
void ThreadFn() 
{ 
    while (1) 
    { 
     Cmd c; 
     ios.RunCmd(c); 
     c.Wait(); 
     //std::cout << c.cnt_ << "\n"; 
    } 
} 

Dado que este ciclo es infinito, ¿por qué no simplemente poner Cmd c; fuera del alcance del tiempo (1) por lo que reutiliza 'c' cada iteración de while (1)?

void ThreadFn() 
{ 
    Cmd c; 

    while (1) 
    { 
     ios.RunCmd(c); 
     c.Wait(); 
     //std::cout << c.cnt_ << "\n"; 
    } 
} 
+0

El código era solo una extrapolación de un programa más grande para demostrar el problema. Había muchos tipos diferentes de comandos inicializados en el constructor, y tampoco había while (1) loop. – Liam