Tal y como podría imaginar, es mucho más FIXMEmessier salir de forma brusca en mitad de la función Runnable::run() que si espera a que esa función llegue al test de isCanceled() (o a algún otro lugar donde el programador esté preparado para salir de la función). Cuando sale de una tarea bloqueada, podría necesitar destruir objetos y liberar recursos. Debido a esto, salir en mitad de un run() de una tarea, más que otra cosa, consiste en lanzar una excepción, por lo que en ZThreads, las excepciones se utilizan para este tipo de terminación. (Esto roza el límite de un uso inapropiado de las excepciones, porque significa que las utiliza para el control de flujo.)[154] Para volver de esta manera a un buen estado conocido a la hora de terminar una tarea, tenga en cuenta cuidadosamente los caminos de ejecución de su código y libere todo correctamente dentro de los bloques catch. Veremos esta técnica en la presente sección.
Para finalizar un hilo bloqueado, la librería ZThread proporciona la función Thread::interrupted(). Esta configura el estado de interrupción para ese hilo. Un hilo con su estado de interrupción configurado lanzará una Interrupted_Exception si está bloqueado o si espera una operación bloqueante. El estado de interrupción será restaurado cuando se haya lanzado la excepción o si la tarea llama a Thread::interrupted(). Como puede ver, Thread::interrupted() proporciona otra forma de salir de su bucle run(), sin lanzar una excepción.
A continuación, un ejemplo que ilustra las bases de interrupt():
//: C11:Interrupting.cpp // Interrupting a blocked thread. //{L} ZThread #include <iostream> #include "zthread/Thread.h" using namespace ZThread; using namespace std; class Blocked : public Runnable { public: void run() { try { Thread::sleep(1000); cout << "Waiting for get() in run():"; cin.get(); } catch(Interrupted_Exception&) { cout << "Caught Interrupted_Exception" << endl; // Exit the task } } }; int main(int argc, char* argv[]) { try { Thread t(new Blocked); if(argc > 1) Thread::sleep(1100); t.interrupt(); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~
Listado 10.26. C11/Interrupting.cpp
Puede ver que, además de la inserción de cout, run() tiene dos puntos donde puede ocurrir el bloqueo: la llamada a Thread::sleep(1000) y la llamada a cin.get(). Dando cualquier argumento por línea de comandos al programa, dirá a main() que se duerma lo suficiente para que la tarea finalice su sleep() y llame a cin.get().[155] Si no le da un argumento, el sleep() de main() se ignora. Ahora, la llamada a interrupt() ocurrirá mientras la tarea está dormida, y verá que esto provoca que una Interrupted_Exception se lance. Si le da un argumento por línea de comandos al programa, descubrirá que la tarea no puede ser interrumpida si está bloqueada en la entrada/salida. Esto es, puede interrumpir cualquier operación bloqueante a excepción de una entrada/salida.[156]
Esto es un poco desconcertante si está creando un hilo que ejecuta entrada/salida porque quiere decir que la entrada/salida tiene posibilidades de bloquear su programa multihilado. El problema es que, de nuevo, C++ no fue diseñado con el sistema de hilos en mente; muy al contrario, FIXMEpresupone que el hilado no existe. Por ello, la librería iostream no ses thread-friendly. Si el nuevo estándar de C++ decide añadir soporte a hilos, la librería iostream podría necesitar ser reconsiderada en el proceso. Bloqueo debido a un mutex.
Si intenta llamar a una función cuyo mutes ha sido adquirido, la tarea que llama será suspendida hasta que el mutex esté accesible. El siguiente ejemplo comprueba si este tipo de bloqueo es interrumpible:
//: C11:Interrupting2.cpp // Interrupting a thread blocked // with a synchronization guard. //{L} ZThread #include <iostream> #include "zthread/Thread.h" #include "zthread/Mutex.h" #include "zthread/Guard.h" using namespace ZThread; using namespace std; class BlockedMutex { Mutex lock; public: BlockedMutex() { lock.acquire(); } void f() { Guard<Mutex> g(lock); // This will never be available } }; class Blocked2 : public Runnable { BlockedMutex blocked; public: void run() { try { cout << "Waiting for f() in BlockedMutex" << endl; blocked.f(); } catch(Interrupted_Exception& e) { cerr << e.what() << endl; // Exit the task } } }; int main(int argc, char* argv[]) { try { Thread t(new Blocked2); t.interrupt(); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~
Listado 10.27. C11/Interrupting2.cpp
La clase BlockedMutex
tiene un
constructor que adquiere su propio
objeto Mutex
y nunca lo libera. Por esa
razón, si intenta llamara a f(), siempre será bloqueado porque
el Mutex
no puede ser
adquirido. En Blocked2
, la función
run() se parará en la llamada blocked.f(). Cuando ejecute el
programa verá que, a diferencia de la llamada a iostream,
interrupt() puede salir de una llamada que está bloqueada por
un mutex.[157] Comprobación de una una interrupción.
Note que cuando llama a interrupt() sobre un hilo, la única vez que ocurre la interrupción es cuando la tarea entra, o ya está dentro, de una operación bloqueante (a excepción, como ya ha visto, del caso de la entrada/salida, donde simplemente
//: C11:Interrupting3.cpp {RunByHand} // General idiom for interrupting a task. //{L} ZThread #include <iostream> #include "zthread/Thread.h" using namespace ZThread; using namespace std; const double PI = 3.14159265358979323846; const double E = 2.7182818284590452354; class NeedsCleanup { int id; public: NeedsCleanup(int ident) : id(ident) { cout << "NeedsCleanup " << id << endl; } ~NeedsCleanup() { cout << "~NeedsCleanup " << id << endl; } }; class Blocked3 : public Runnable { volatile double d; public: Blocked3() : d(0.0) {} void run() { try { while(!Thread::interrupted()) { point1: NeedsCleanup n1(1); cout << "Sleeping" << endl; Thread::sleep(1000); point2: NeedsCleanup n2(2); cout << "Calculating" << endl; // A time-consuming, non-blocking operation: for(int i = 1; i < 100000; i++) d = d + (PI + E) / (double)i; } cout << "Exiting via while() test" << endl; } catch(Interrupted_Exception&) { cout << "Exiting via Interrupted_Exception" << endl; } } }; int main(int argc, char* argv[]) { if(argc != 2) { cerr << "usage: " << argv[0] << " delay-in-milliseconds" << endl; exit(1); } int delay = atoi(argv[1]); try { Thread t(new Blocked3); Thread::sleep(delay); t.interrupt(); } catch(Synchronization_Exception& e) { cerr << e.what() << endl; } } ///:~
Listado 10.28. C11/Interrupting3.cpp