2010-06-11 11 views
5

Tengo que escribir un programa que realiza cálculos altamente computacionales intensivos. El programa puede funcionar por varios días. El cálculo se puede separar fácilmente en diferentes subprocesos sin la necesidad de datos compartidos. Quiero una GUI o un servicio web que me informe del estado actual.Señales e hilos: ¿decisión de diseño buena o mala?

Mi diseño actual usa BOOST :: signals2 y BOOST :: thread. Compila y hasta el momento funciona como se esperaba. Si un hilo terminó una iteración y hay nuevos datos disponibles, llama a una señal que está conectada a una ranura en la clase de la GUI.

Mi pregunta (s):

  • ¿Es esta combinación de señales y los hilos de una buena idea? En otro foro, alguien aconsejó a otra persona que no "siga por este camino".
  • ¿Hay posibles peligros mortales cerca que no pude ver?
  • ¿Es realista mi expectativa de que sea "fácil" utilizar mi clase de GUI para proporcionar una interfaz web o un QT, un VTK o una ventana cualquiera?
  • ¿Hay una alternativa más inteligente (como otras librerías de refuerzo) que pasé por alto?

siguiente código se compila con

código
g++ -Wall -o main -lboost_thread-mt <filename>.cpp 

sigue:

#include <boost/signals2.hpp> 
#include <boost/thread.hpp> 
#include <boost/bind.hpp> 

#include <iostream> 
#include <iterator> 
#include <string> 

using std::cout; 
using std::cerr; 
using std::string; 

/** 
* Called when a CalcThread finished a new bunch of data. 
*/ 
boost::signals2::signal<void(string)> signal_new_data; 

/** 
* The whole data will be stored here. 
*/ 
class DataCollector 
{ 
    typedef boost::mutex::scoped_lock scoped_lock; 
    boost::mutex mutex; 

public: 
    /** 
    * Called by CalcThreads call the to store their data. 
    */ 
    void push(const string &s, const string &caller_name) 
    { 
     scoped_lock lock(mutex); 
     _data.push_back(s); 
     signal_new_data(caller_name); 
    } 

    /** 
    * Output everything collected so far to std::out. 
    */ 
    void out() 
    { 
     typedef std::vector<string>::const_iterator iter; 
     for (iter i = _data.begin(); i != _data.end(); ++i) 
      cout << " " << *i << "\n"; 
    } 

private: 
    std::vector<string> _data; 
}; 

/** 
* Several of those can calculate stuff. 
* No data sharing needed. 
*/ 
struct CalcThread 
{ 
    CalcThread(string name, DataCollector &datcol) : 
     _name(name), _datcol(datcol) 
    { 

    } 

    /** 
    * Expensive algorithms will be implemented here. 
    * @param num_results how many data sets are to be calculated by this thread. 
    */ 
    void operator()(int num_results) 
    { 
     for (int i = 1; i <= num_results; ++i) 
     { 
      std::stringstream s; 
      s << "["; 
      if (i == num_results) 
       s << "LAST "; 
      s << "DATA " << i << " from thread " << _name << "]"; 
      _datcol.push(s.str(), _name); 
     } 
    } 

private: 
    string _name; 
    DataCollector &_datcol; 
}; 

/** 
* Maybe some VTK or QT or both will be used someday. 
*/ 
class GuiClass 
{ 
public: 
    GuiClass(DataCollector &datcol) : 
     _datcol(datcol) 
    { 

    } 

    /** 
    * If the GUI wants to present or at least count the data collected so far. 
    * @param caller_name is the name of the thread whose data is new. 
    */ 
    void slot_data_changed(string caller_name) const 
    { 
     cout << "GuiClass knows: new data from " << caller_name << std::endl; 
    } 

private: 
    DataCollector & _datcol; 

}; 

int main() 
{ 
    DataCollector datcol; 

    GuiClass mc(datcol); 
    signal_new_data.connect(boost::bind(&GuiClass::slot_data_changed, &mc, _1)); 

    CalcThread r1("A", datcol), r2("B", datcol), r3("C", datcol), r4("D", 
      datcol), r5("E", datcol); 

    boost::thread t1(r1, 3); 
    boost::thread t2(r2, 1); 
    boost::thread t3(r3, 2); 
    boost::thread t4(r4, 2); 
    boost::thread t5(r5, 3); 

    t1.join(); 
    t2.join(); 
    t3.join(); 
    t4.join(); 
    t5.join(); 

    datcol.out(); 

    cout << "\nDone" << std::endl; 
    return 0; 
} 
+0

Si bien este ejemplo particular está bien, debe tener cuidado, ya que no está protegiendo su vector con el mutex en la función 'out' – nos

Respuesta

12

Es esta combinación de señales y hilos de una buena idea? Yo otro foro alguien aconsejó a otra persona no a "ir por este camino".

Parece ser sonido. ¿Puedes proporcionar un enlace al otro hilo? ¿Estaban explicando su razonamiento?

¿Hay posibles peligros mortales cerca que no pude ver?

Si es que no los veo también. Lo que hay que tener cuidado es que las notificaciones son seguros para subprocesos (la activación de la señal no se enciende contextos de hilos, a su GuiClass::slot_data_changed debería llamarse de todos los otros hilos.

¿Es mi expectativa realista que será "fácil" de usar mi clase de interfaz gráfica de usuario para proporcionar una interfaz web o un cuarto de galón, un VTK o una ventana de lo que sea?

no va a ser fácil. para solucionar este problema, habría que hacer su interruptor de notificación contexto de enhebrado. Esto es lo que haría:

Haga que su GuiClass sea un n clase base abstracta, implementando su propia cola de mensajes. Cuando tus subprocesos llaman a GuiClass::slot_data_changed, bloqueas un mutex y publicas una copia de la notificación recibida en una cola de mensajes interna (private:). En el hilo del GuiClass crea una función que bloquea el mutex y busca notificaciones en la cola.Esta función debe ejecutarse en el hilo del código del cliente (en el hilo de las clases concretas que se especializan en el resumen GuiClass).

Ventajas:

  • su clase base encapsula y aísla el cambio de contexto hilo, de forma transparente para su especialización.

Desventajas:

  • su código de cliente tiene que o bien ejecutar el método de votación o permitir que se ejecute (como una función de procesamiento de hilo).

  • que es un poco complicado :)

¿Es mi expectativa realista que será "fácil" de usar mi clase GUI para proporcionar una interfaz web o de un cuarto de galón, un VTK o una ventana cualquiera?

No lo veo, pero no es tan fácil. Además del cambio de contexto del hilo, puede haber otros problemas que me faltan por el momento.

¿Hay una alternativa más inteligente (como otras librerías boost) que pasado por alto?

No hay otras libretas de refuerzo, pero la forma en que escribió sus hilos no es buena: las uniones se realizan secuencialmente en su código. Para tener solo un join para todos los hilos, use un boost :: thread_group.

En lugar de:

boost::thread t1(r1, 3); 
boost::thread t2(r2, 1); 
boost::thread t3(r3, 2); 
boost::thread t4(r4, 2); 
boost::thread t5(r5, 3); 

t1.join(); 
t2.join(); 
t3.join(); 
t4.join(); 
t5.join(); 

usted tiene:

boost::thread_group processors; 
processors.create_thread(r1, 3); 
// the other threads here 

processors.join_all(); 

Editar: Un contexto de subproceso es todo lo que es específico de un hilo conductor en particular (de almacenamiento de hilo específico, la pila de ese hilo, cualquier excepción lanzada en el contexto de ese hilo, etc.).

Cuando tiene varios contextos de subprocesos en la misma aplicación (múltiples subprocesos) necesita sincronizar el acceso a los recursos creados dentro de un contexto de subproceso y acceder desde diferentes subprocesos (utilizando primitivas de bloqueo).

Por ejemplo, digamos que usted tiene a, una instancia de class A [corriendo en hilo de TA] haciendo algunas cosas y b, una instancia de class B [se ejecuta en el contexto de hilo tB] y b quiere decir a algo.

El "quiere decir algo a" parte significa que b quiere llamar a.something()a.something() y serán llamados en el contexto de la TB (en la pila del hilo B).

Para cambiar esto (para que a.something() se ejecute en el contexto de tA), debe cambiar el contexto del subproceso. Esto significa que en lugar de b diciendo a "ejecutar A::something()", b dice a "ejecutar A :: something()` en su propio contexto de hilo ".

pasos de implementación clásicos:

  • b envía un mensaje a a desde dentro tB

  • a encuestas para los mensajes desde dentro tA

  • Cuando a encuentra el mensaje de b, se ejecuta a.something() en sí mismo, dentro de tA.

Esta es la conmutación de contextos de roscado (la ejecución de A::something se ejecutará en tA en lugar de la tuberculosis, ya que habría sido si llamada directamente de b).

Desde el enlace que proporcionó, parece que ya está implementado por boost::asio::io_service, por lo que si usa eso, no tiene que implementarlo usted mismo.

+0

+1: puntos buenos – neuro

+0

La advertencia sobre hilos y señales se puede encontrar aquí: http://www.gamedev.net/community/forums/topic.asp?topic_id=553476 No entiendo muy bien lo que quiere decir con el término 'cambio de contexto de hilo'. Me temo que este término solo falta en mi vocabulario. – Jens

+0

@Jens, mira mi edición anterior. – utnapistim

7

Hay un escollo muy importante:

Por lo que yo entiendo signals2's thread safety las ranuras se ejecutan en el señalización hilo. La mayoría de las bibliotecas de GUI (especialmente Qt y OpenGL) deben hacer todo el dibujo de un solo hilo. Esto no es un problema en general, pero requiere un poco de cuidado. Tiene dos opciones:

La primera es que tiene cuidado de no hacer ningún dibujo dentro de GuiClass::slot_data_changed (ya que usa Qt eche un vistazo a QCoreApplication :: postEvent (lo siento, no se permite publicar el enlace a Qt docu))

La segunda es que crea usted mismo una cola de mensajes, lo que guarda las invocaciones de las ranuras y las ejecuta en el hilo de la GUI. Esto es algo más oneroso, pero también más seguro, porque sus clases de GUI se pueden escribir sin preocuparse por la seguridad de los hilos.

+0

+1 para dos alternativas bien descritas – Jens

Cuestiones relacionadas