Usando el grupo de subprocesos para la simulación: boost-thread y boost-asio

Me gustaria usar boost::asio para configurar un grupo de subprocesos. Mi pregunta es: ¿cómo puedo adjuntar datos específicos a cada uno de los hilos creados y cómo puedo administrar los resultados individuales?

Para ser más específico, escribí una clase Simulation que realiza la simulación a través de un método que toma algunos parámetros como entrada. Esta clase contiene todos los datos necesarios para el cálculo. Como los datos no son demasiado grandes, me gustaría duplicarlos para usar una instancia diferente de la clase. Simulation en cada hilo de la piscina.

Me gustaría hacer algo como esto: (La configuración de un grupo de subprocesos se explica aquí: SO y recetas asio)

class ParallelSimulation
{
  public:
    static const std::size_t N = 10;

  protected:
    std::vector< boost::shared_ptr<Simulation> > simuInst; // N copy of a reference instance.

  public:

    ...

    // Simulation with a large (>>N) number of inputs
    void eval( std::vector< SimulationInput > inputs )
    {
      // Creation of the pool using N threads
      asio::io_service io_service;
      asio::io_service::work work(io_service);
      boost::thread_group threads;
      for (std::size_t i = 0; i < N; ++i)
        threads.create_thread(boost::bind(&asio::io_service::run, &io_service));

      // Here ? Attaching the duplicates instances of class Simulation ?

      // Adding tasks
      for( std::size_t i = 0, i_end = inputs.size(); i<i_end; ++i)
        io_service.post(...); // add simulation with inputs[i] to the queue

      // How to deal with outputs ?  

      // End of the tasks
      io_service.stop();
      threads.join_all();
    }
};

Tal vez la técnica utilizada para configurar un grupo de subprocesos (utilizando boost::asio) no se adapta a mi problema. ¿Tendrías alguna sugerencia? Gracias.

preguntado el 12 de junio de 12 a las 16:06

2 Respuestas

¡Aquí están los resultados de mi investigación!

La simulación distribuida se basa en una clase principal. DistributedSimulation utilizando dos clases de implementación: impl::m_io_service y impl::dispatcher.

La boost::asio el grupo de subprocesos se basa en adjuntar io_service::run() método a diferentes hilos.
La idea es redefinir este método e incluir un mecanismo para identificar el hilo actual. La solución a continuación se basa en el almacenamiento local de subprocesos boost::thread_specific_ptr of boost::uuid. Después de leer el comentario de Tres, creo que identificar el hilo usando boost::thread::id es una solución mejor (pero equivalente y no muy diferente).
Finalmente, se utiliza otra clase para enviar los datos de entrada a las instancias de la clase Simulación. Esta clase crea varias instancias de la misma clase Simulación y las usa para calcular los resultados en cada subproceso.

namespace impl {

  // Create a derived class of io_service including thread specific data (a unique identifier of the thread)
  struct m_io_service : public boost::asio::io_service
  {
    static boost::thread_specific_ptr<boost::uuids::uuid> ptrSpec_;

    std::size_t run()
    {
      if(ptrSpec_.get() == 0) 
        ptrSpec_.reset(new boost::uuids::uuid(boost::uuids::random_generator()())  );

      return boost::asio::io_service::run();
    }
  };


  // Create a class that dispatches the input data over the N instances of the class Simulation
  template <class Simulation>
  class dispatcher
  {  
    public:
      static const std::size_t N = 6;

      typedef Simulation::input_t input_t;
      typedef Simulation::output_t output_t;

      friend DistributedSimulation;

    protected:
      std::vector< boost::shared_ptr<Simulation> > simuInst;
      std::vector< boost::uuids::uuid >            map;

    public:

      // Constructor, creating the N instances of class Simulation
      dispatcher( const Simulation& simuRef) 
      {
        simuInst.resize(N);
        for(std::size_t i=0; i<N; ++i)
          simuInst[i].reset( simuRef.clone() );
      }

      // Record the unique identifiers and do the calculation using the right instance of class Simulation
      void dispatch( const Simulation::input_t& in  )
      {
        if( map.size() == 0 ) {
          map.push_back(*m_io_service::ptrSpec_);
          simuInst[0]->eval(in, *m_io_service::ptrSpec_);
        }    
        else {
          if( map.size() < N ) {
            map.push_back(*m_io_service::ptrSpec_);
            simuInst[map.size()-1]->eval(in, *m_io_service::ptrSpec_);
          }
          else {
            for(size_t i=0; i<N;++i) {
              if( map[i] == *m_io_service::ptrSpec_) {
                simuInst[i]->eval(in, *m_io_service::ptrSpec_);
                return;
              }
            }
          }
        }
      }
  };

  boost::thread_specific_ptr<boost::uuids::uuid> m_io_service::ptrSpec_;
}


  // Main class, create a distributed simulation based on a class Simulation
  template <class Simulation>
  class DistributedSimulation
  {
  public:
    static const std::size_t N = impl::dispatcher::N;

  protected: 
    impl::dispatcher _disp;

  public:
    DistributedSimulation() : _disp( Simulation() ) {}

    DistributedSimulation(Simulation& simuRef) 
    : _disp( simuRef ) {  }


    // Simulation with a large (>>N) number of inputs
    void eval( const std::vector< Simulation::input_t >& inputs, std::vector< Simulation::output_t >& outputs )
    {

      // Clear the results from a previous calculation (and stored in instances of class Simulation)
      ...

      // Creation of the pool using N threads
      impl::m_io_service io_service;
      boost::asio::io_service::work work(io_service);
      boost::thread_group threads;
      for (std::size_t i = 0; i < N; ++i)
        threads.create_thread(boost::bind(&impl::m_io_service::run, &io_service));

      // Adding tasks
      for( std::size_t i = 0, i_end = inputs.size(); i<i_end; ++i)
        io_service.post( boost::bind(&impl::dispatcher::dispatch, &_disp, inputs[i]) );

      // End of the tasks
      io_service.stop();
      threads.join_all();

      // Gather the results iterating through instances of class simulation
      ...
    }
  };

Editar

El siguiente código es una actualización de mi solución anterior, teniendo en cuenta el comentario de Tres. Como dije antes, ¡es mucho más legible simple!

  template <class Simulation>
  class DistributedSimulation
  {
    public:
      typedef typename Simulation::input_t  input_t;
      typedef typename Simulation::output_t output_t;

      typedef boost::shared_ptr<Simulation> SimulationSPtr_t;
      typedef boost::thread::id             id_t;      
      typedef std::map< id_t, std::size_t >::iterator IDMapIterator_t;

    protected: 
      unsigned int                    _NThreads;   // Number of threads
      std::vector< SimulationSPtr_t > _simuInst;   // Instances of class Simulation
      std::map< id_t, std::size_t >   _IDMap;      // Map between thread id and instance index.

    private:
      boost::mutex _mutex;

    public:

      DistributedSimulation(  ) {}

      DistributedSimulation( const Simulation& simuRef, const unsigned int NThreads = boost::thread::hardware_concurrency() ) 
        { init(simuRef, NThreads); }

      DistributedSimulation(const DistributedSimulation& simuDistrib) 
        { init(simuRef, NThreads); }

      virtual ~DistributedSimulation() {}

      void init(const Simulation& simuRef, const unsigned int NThreads = boost::thread::hardware_concurrency())
      {
        _NThreads = (NThreads == 0) ? 1 : NThreads;
        _simuInst.resize(_NThreads);
        for(std::size_t i=0; i<_NThreads; ++i)
          _simuInst[i].reset( simuRef.clone() );
        _IDMap.clear();
      }


      void dispatch( const input_t& input )
      {
        // Get current thread id
        boost::thread::id id0 = boost::this_thread::get_id();

        // Get the right instance 
        Simulation* sim = NULL;        
        { 
          boost::mutex::scoped_lock scoped_lock(_mutex);
          IDMapIterator_t it = _IDMap.find(id0);
          if( it != _IDMap.end() )
            sim = _simuInst[it->second].get();
        } 

        // Simulation
        if( NULL != sim )
          sim->eval(input);
      }


      // Distributed evaluation.
      void eval( const std::vector< input_t >& inputs, std::vector< output_t >& outputs )
      {
        //--Initialisation
        const std::size_t NInputs = inputs.size();

        // Clear the ouptuts f(contained in instances of class Simulation) from a previous run
        ...

        // Create thread pool and save ids
        boost::asio::io_service io_service;
        boost::asio::io_service::work work(io_service);
        boost::thread_group threads;
        for (std::size_t i = 0; i < _NThreads; ++i)
        {
          boost::thread* thread_ptr = threads.create_thread(boost::bind(&boost::asio::io_service::run, &io_service));
          _IDMap[ thread_ptr->get_id() ] = i;
        }

        // Add tasks
        for( std::size_t i = 0; i < NInputs; ++i)
          io_service.post( boost::bind(&DistributedSimulation::dispatch, this, inputs[i]) );

        // Stop the service
        io_service.stop();
        threads.join_all();

        // Gather results (contained in each instances of class Simulation)
        ...
      }
  }; 

Respondido 25 Abr '13, 13:04

Esto debería funcionar bien para su aplicación. Cuando haces la llamada a io_service.post, pasará en la función de simulación con inputs[i] como parámetro. En esa función (presumiblemente una función miembro de Simulation), simplemente almacene el resultado del cálculo en el Simulation objeto, luego itere sobre los objetos después de unir los subprocesos para recopilar la salida.

Puedes pasar i como parámetro también, si necesita identificar el hilo específico que hizo el trabajo. Esto supone que la recopilación de la salida después de que se hayan completado las simulaciones está bien.

Si necesita acceder a la salida mientras se está ejecutando, solo tenga la función post una tarea de salida para io_service según sea necesario. ¡Asegúrese de proteger cualquier estructura de datos compartida con un mutex!

Respondido el 12 de junio de 12 a las 20:06

Gracias por su respuesta. Pero no veo cómo usa las N instancias de la clase Simulación para asegurarse de que el hilo i calcular los resultados con la instancia i (y por lo tanto utiliza sus propios datos para hacer el trabajo). La forma de lidiar con las salidas parece estar bien, ¡gracias! - gleeen.gould

@gleeen.gould ¿Puede aclarar por qué es importante que use un hilo? i para calcular el trabajo para la simulación i? Es posible (llamar thread->get_id() cuando lo crees, pásalo a simulation[i], entonces ten simulation[i]La función de trabajo comprueba cuando se ejecuta que boost::this_thread::get_id() partidos, si no regresa, sino sigue), pero no creo que sea necesario. - Tres

Mire mi solución, espero que esto aclare mis objetivos. - gleeen.gould

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.