Para controlar un objeto Runnable con un hilo, crea un objeto Thread separado y utiliza un pontero Runnable al constructor de Thread. Esto lleva a cabo la inicialización del hilo y, después, llama a run ( ) de Runnable como un hilo capaz de ser interrumpido. Manejando LiftOff con un hilo, el ejemplo siguiente muestra como cualquier tarea puede ser ejecutada en el contexto de cualquier otro hilo:
//: C11:BasicThreads.cpp // The most basic use of the Thread class. //{L} ZThread #include <iostream> #include "LiftOff.h" #include "zthread/Thread.h" using namespace ZThread; using namespace std; int main() { try { Thread t(new LiftOff(10)); cout << "Waiting for LiftOff" << endl; } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~
Listado 10.3. C11/BasicThreads.cpp
Synchronization_Exception forma parte de la librería ZThread y la clase base para todas las excepciones de ZThread. Se lanzará si hay un error al crear o usar un hilo.
Un constructor de Thread
sólo necesita un
puntero a un objeto Runnable. Al crear un objeto
Thread
se efectuará la incialización
necesaria del hilo y después se llamará a
Runnable::run()
Puede añadir más hilos fácilmente para controlar más tareas. A continuación, puede ver cómo los hilos se ejecutan con algún otro:
//: C11:MoreBasicThreads.cpp // Adding more threads. //{L} ZThread #include <iostream> #include "LiftOff.h" #include "zthread/Thread.h" using namespace ZThread; using namespace std; int main() { const int SZ = 5; try { for(int i = 0; i < SZ; i++) Thread t(new LiftOff(10, i)); cout << "Waiting for LiftOff" << endl; } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~
Listado 10.4. C11/MoreBasicThreads.cpp
El segundo argumento del constructor
de LiftOff
identifica cada tarea. Cuando
ejecute el programa, verá que la ejecución de las distintas
tareas se mezclan a medida que los hilos entran y salen de su
ejecución. Este intercambio está controlado automáticamente por
el planificador de hilos. Si tiene múltiples procesadores en su
máquina, el planificador de hilos distribuirá los hilos entre
los procesadores de forma transparente.
El bucle for puede parecer un poco extraño a priori ya que se
crea localmente dentro del bucle for e inmediatamente después
sale del ámbito y es destruído. Esto hace que parezca que el
hilo propiamente dicho pueda perderse inmediatamente, pero puede
ver por la salida que los hilos, en efecto, están en ejecución
hasta su finalización. Cuando crea un
objeto Thread
, el hilo asociado se
registra en el sistema de hilos, que lo mantiene vivo. A pesar
de que el objeto Thread
local se pierde,
el hilo sigue vivo hasta que su tarea asociada termina. Aunque
puede ser poco intuitivo desde el punto de vista de C++, el
concepto de hilos es la excepción de la regla: un hilo crea un
hilo de ejecución separado que persiste después de que la
llamada a función finalice. Esta excepción se refleja en la
persistencia del hilo subyacente después de que el objeto
desaparezca.
Como se dijo anteriormente, uno de las motivaciones para usar hilos es crear interfaces de usuario interactivas. Aunque en este libro no cubriremos las interfaces gráficas de usuario, verá un ejemplo sencillo de una interfaz de usuario basada en consola.
El siguiente ejemplo lee líneas de un archivo y las imprime a la consola, durmiéndose (suspender el hilo actual) durante un segundo después de que cada línea sea mostrada. (Aprenderá más sobre el proceso de dormir hilos en el capítulo.) Durante este proceso, el programa no busca la entrada del usuario, por lo que la IU no es interactiva:
//: C11:UnresponsiveUI.cpp {RunByHand} // Lack of threading produces an unresponsive UI. //{L} ZThread #include <iostream> #include <fstream> #include <string> #include "zthread/Thread.h" using namespace std; using namespace ZThread; int main() { cout << "Press <Enter> to quit:" << endl; ifstream file("UnresponsiveUI.cpp"); string line; while(getline(file, line)) { cout << line << endl; Thread::sleep(1000); // Time in milliseconds } // Read input from the console cin.get(); cout << "Shutting down..." << endl; } ///:~
Listado 10.5. C11/UnresponsiveUI.cpp
Para hacer este programa interactivo, puede ejecutar una tarea que muestre el archivo en un hilo separado. De esta forma, el hilo principal puede leer la entrada del usuario, por lo que el programa se vuelve interactivo:
//: C11:ResponsiveUI.cpp {RunByHand} // Threading for a responsive user interface. //{L} ZThread #include <iostream> #include <fstream> #include <string> #include "zthread/Thread.h" using namespace ZThread; using namespace std; class DisplayTask : public Runnable { ifstream in; string line; bool quitFlag; public: DisplayTask(const string& file) : quitFlag(false) { in.open(file.c_str()); } ~DisplayTask() { in.close(); } void run() { while(getline(in, line) && !quitFlag) { cout << line << endl; Thread::sleep(1000); } } void quit() { quitFlag = true; } }; int main() { try { cout << "Press <Enter> to quit:" << endl; DisplayTask* dt = new DisplayTask("ResponsiveUI.cpp"); Thread t(dt); cin.get(); dt->quit(); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } cout << "Shutting down..." << endl; } ///:~
Listado 10.6. C11/ResponsiveUI.cpp
Ahora el hilo main() puede responder inmediatamente cuando
pulse Return e invocar quit()
sobre DisplayTask
.
Este ejemplo también muestra la necesidad de una comunicación
entre tareas - la tarea en el hilo main() necesita parar
al DisplayTask
. Dado que tenemos un
puntero a DisplayTask, puede pensar que bastaría con llamar al
destructor de ese puntero para matar la tarea, pero esto hace
que los programas sean poco fiables. El problema es que la
tarea podría estar en mitad de algo importante cuando lo
destruye y, por lo tanto, es probable que ponga el programa en
un estado inestable. En este sentido, la propia tarea decide
cuando es seguro terminar. La manera más sencilla de hacer
esto es simplemente notificar a la tarea que desea detener
mediante una bandera booleana. Cuando la tarea se encuentre en
un punto estable puede consultar esa bandera y hacer lo que
sea necesario para limpiar el estado después de regresar de
run(). Cuando la tarea vuelve de
run(), Thread
sabe que la tarea se ha
completado.
Aunque este programa es lo suficientemente simple para que no haya problemas, hay algunos pequeños defectos respecto a la comunicación entre pilas. Es un tema importante que se cubrirá más tarde en este capítulo.