Usando el grupo de subprocesos para la simulación: boost-thread y boost-asio
Frecuentes
Visto 2,116 veces
4
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.
2 Respuestas
1
¡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
0
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
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas c++ multithreading optimization boost boost-asio or haz tu propia pregunta.
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 instanciai
(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óni
? Es posible (llamarthread->get_id()
cuando lo crees, pásalo asimulation[i]
, entonces tensimulation[i]
La función de trabajo comprueba cuando se ejecuta queboost::this_thread::get_id()
partidos, si no regresa, sino sigue), pero no creo que sea necesario. - TresMire mi solución, espero que esto aclare mis objetivos. - gleeen.gould