Main logo La Página de DriverOp

Capítulo 2. Crear un hilo de ejecución en Delphi.

En este capítulo:

  • Un diagrama de intervalos.
  • Nuestro primer hilo no-VCL.
  • ¿Qué hace exactamente este programa?
  • Cuestiones, problemas y sorpresas.
  • Cuestiones en la inicialización.
  • Cuestiones en la comunicación.
  • Cuestiones de terminación.

Un diagrama de intervalos.

Antes de meterse en los detalles de crear hilos de ejecución, y ejecutar código independiente del hilo principal de la aplicación, es necesario introducir un nuevo tipo de diagrama ilustrativo de la dinámica de la ejecución de hilos. Esto nos ayudará cuando comencemos a diseñar y crear programas multihilo. Considera esta simple aplicación.

La aplicación tiene un hilo de ejecución: el hilo principal de la VCL. El progreso de este hilo puede ser ilustrado con un diagrama que muestra el estado del hilo en la aplicación a través del tiempo. El progreso de este hilo está representado por una línea, y el tiempo fluye en forma descendente en la página. Incluí una referencia en este diagrama que se aplica a todos los subsecuentes diagramas de hilos de ejecución.

Nótese que este diagrama no indica mucho acerca de los algoritmos que se ejecutan. En cambio, ilustra el orden de los eventos a través del tiempo y el estado de los hilos de ejecución entre ese tiempo. La distancia entre diferentes puntos del diagrama no es importante, pero sí el ordenamiento vertical de esos puntos. Hay mucha información que se puede extraer de este diagrama.

  • El hilo en esta aplicación no se ejecuta continuamente. Puede haber largos períodos de tiempo durante los cuales no recibe estímulos externos y no está llevando ningún cálculo ni ningún otro tipo de operación. La memoria y los recursos ocupados por la aplicación existen y la ventana está aún en la pantalla, pero ningún código está siendo ejecutado por el microprocesador.
  • La aplicación es inicializada y el hilo principal es ejecutado. Una vez que se crea la ventana principal, no tiene más trabajo que hacer y se reposa sobre una pieza de código VCL conocida como el bucle de mensajes de la aplicación que espera más mensajes del sistema operativo. Si no hay más mensajes para ser procesados, el sistema operativo suspende el hilo y el hilo de ejecución está ahora suspendido.
  • En un momento posterior, el usuario hace clic en el botón, para mostrar el mensaje de texto. El sistema operativo despierta (o reanuda) el hilo principal, y le entrega un mensaje indicando que un botón ha sido presionado. El hilo principal está ahora activo nuevamente.
  • Este proceso de suspensión – reanudación ocurre varias veces en el tiempo. Ilustré una espera de confirmación del usuario para cerrar la caja de mensajes y espera que el botón de cerrar sea presionado. En la práctica, muchos otros mensajes pueden ser recibidos.

Nuestro primer hilo no-VCL

A pesar de que el API Win32 provee un extenso soporte multihilo, al momento de crear y destruir hilos de ejecución, el VCL tiene una clase muy útil, TThread, que abstrae la mayoría de las técnicas para crear un hilo, provee una simplificación muy útil, e intenta evitar que el programador caiga en una de las muchas trampas indeseables que esta nueva disciplina provee. Yo recomiendo su uso. La ayuda de Delphi provee una guía razonable para crear diferentes tipos de hilos, de modo que no voy a mencionar mucho sobre las secuencias de menú necesarias para crear un hilo de ejecución independiente mas allá de sugerir que el lector seleccione File | New… y luego elija Thread Object.

Este ejemplo en particular consiste en un programa que calcula si un número en particular es un número primo o no. Contiene dos units, una  con un formulario convencional, y otra con un objeto hilo. Más o menos funciona; de hecho tiene algunos rasgos indeseables que ilustran algunos de los problemas básicos que los programadores multihilo deben considerar. Discutiremos el modo de evitar estos problemas más tarde. Aquí está el código fuente del formulario y aquí está el código fuente del objeto hilo.

¿Qué hace exactamente este programa?

Cada vez que el botón “Spawn” es presionado, el programa crea un nuevo objeto hilo, inicializa algunos campos en el objeto y luego hace andar al hilo. Tomando el número ingresado, el hilo se aparta calculando si el número es primo y una vez que ha terminado el cálculo, muestra una caja de mensajes indicando si el número es primo. Estos hilos son concurrentes, mas allá de que se tenga una máquina uniprocesador o multiprocesador; desde el punto de vista del usuario, estos se ejecutan en forma simultánea. Además, este programa no limita el número de hilos creados. Como resultado, se puede demostrar que hay una concurrencia real de la siguiente manera:

  • Como he comentado un comando de salida en la rutina que determina si el número es primo, el tiempo que corre el hilo es directamente proporcional al tamaño del número ingresado. He notado que con un valor de aproximadamente 224, el hilo necesita entre 10 y 20 segundos en completarse. Encuentra un valor que produzca una demora similar en tu máquina.
  • Ejecuta el programa, introduce un número grande y haz clic en el botón.
  • Inmediatamente introduce un número pequeño (digamos, 42) y haz clic en el botón nuevamente. Notarás que el resultado para el número pequeño se produce antes que el resultado para el número grande, aún cuando comenzamos el hilo con el número grande primero. El diagrama de abajo ilustra la situación.

Cuestiones, problemas y sorpresas.

Hasta este punto, el tema de la sincronización se ve espinoso. Una vez que el hilo principal llamó a Resume en un hilo “funcionando”, el programa principal no puede asumir absolutamente nada sobre el estado del hilo en funcionamiento y viceversa. Es completamente posible que el hilo en funcionamiento complete su ejecución antes de que el progreso del hilo principal de VCL termine. De hecho, para números pequeños que toman menos de una veinteava de segundo en calcularse, es absolutamente probable. De forma similar, el hilo en funcionamiento no puede asumir nada acerca del estado de progreso del hilo principal. Todo está a merced del administrador de tareas de Win32. Hay tres “factores de gracia” que uno encuentra aquí: cuestiones de Iniciación, cuestiones de Comunicación y cuestiones de Terminación.

Cuestiones de iniciación.

Delphi hace que lidiar con las cuestiones de iniciación de hilos de ejecución sea cosa fácil. Antes de hacer correr un hilo, uno suele desear establecer algunos estados en el hilo. Creando un hilo suspendido (un argumento soportado por el constructor), uno puede estar seguro de que el código no es ejecutado hasta que el hilo es reanudado (Resume). Esto significa que el hilo principal de VCL puede leer y modificar datos en el objeto del hilo de una forma segura, y con la garantía de que serán actualizados y validados en el momento en que el hilo hijo comienza a ejecutarse.

En el caso de este programa, las propiedades del hilo “FreeOnTerminate” (liberarse cuando termine) y “TestNumber” (la variable), son establecidas antes de que el hilo comience a ejecutarse. Si este no fuera el caso, el funcionamiento del hilo quedaría indefinido. Si no deseas crear el hilo suspendido, entonces estarás pasándole el problema de la inicialización a la siguiente categoría: cuestiones de comunicación.

Cuestiones de comunicación.

Esto ocurre cuando tienes dos hilos que están ambos corriendo y necesitas comunicarte entre ellos de algún modo. Este programa evade el problema simplemente no teniendo nada que comunicar entre los hilos separados. De más esta decir que si no proteges todas tus operaciones en datos compartidos (en el más estricto sentido de “protección”), tu programa no será confiable. Si no tienes una adecuada sincronización o un sólido control de concurrencia, lo siguiente será imposible:

  • Acceder a cualquier tipo de datos compartidos entre dos hilos.
  • Interactuar con partes inseguras del VCL desde un hilo no-VCL.
  • Intentar relegar operaciones relacionadas con gráficas en hilos independientes.

Aún haciendo las cosas tan simples como tener dos hilos accediendo a una variable de tipo integer compartida puede resultar en un completo desastre. Accesos no sincronizados a recursos compartidos o llamadas de VCL resultarán en muchas horas de tensos debugueo, considerable confusión y eventuales internaciones en el hospital mental más cercano. Hasta que aprendas la técnica apropiada para hacer esto en los capítulos siguientes, no lo hagas.

¿La buena noticia? Puedes hacer todo lo de arriba si usas el mecanismo correcto para controlar la concurrencia, ¡y ni siquiera es difícil! Veremos un modo sencillo de resolver aspectos de comunicación a través de la VCL en el próximo capitulo, y más elegantes (y complicados) métodos luego.

Cuestiones de terminación.

Los hilos de ejecución, al igual que otros objetos de Delphi, involucran la asignación de memoria y recursos. No debería sorprender saber la importancia de que el hilo termine adecuadamente, algo que el programa de este ejemplo hace mal. Hay dos enfoques posibles para el problema de la liberación del hilo.

El primero es dejar que el hilo maneje el problema por sí mismo. Esto es principalmente usado para hilos que, o comunica los resultados de la ejecución del hilo al hilo principal de la VCL antes de terminar o no poseen ninguna información que resulte útil para otros hilos al momento de terminar. En estos casos, el programador puede activar la variable “FreeOnTerminate” en el objeto hilo, y se liberará cuando termine.

La segunda es que el hilo principal de VCL lea datos del hilo en funcionamiento cuando este haya terminado, y luego liberar el hilo. Esto es tratado en el capítulo 4.

He hecho a un lado el tema de comunicar los resultados de vuelta al hilo principal al hacer que el hilo hijo presenta la respuesta al usuario mediante una llamada a “ShowMessage”. Esto no involucra ningún tipo de comunicación con el hilo principal de VCL y el llamado a ShowMessage es seguro entre hilos, de modo que el VCL no tiene problemas. Como resultado de esto, puedo usar el primer enfoque de liberación del hilo, dejando que el hilo se libere a sí mismo. A pesar de esto, el programa de ejemplo ilustra una característica indeseable al hacer que los hilos se liberen a sí mismos:

Como podrá notar, hay dos cosas que pueden suceder. La primera es que intentemos salir del programa, mientras el hilo continua activo y calculando. La segunda es que intentemos salir del programa mientras éste esta suspendido. El primer caso es bastante malo: la aplicación termina sin siquiera asegurarse de que no haya hilos funcionando. El código de liberación de Delphi y Windows hace que la aplicación termine bien. Lo segundo que podría pasar no es tan bellamente manejable, ya que el hilo está suspendido en algún lugar dentro de las entrañas del sistema de mensajería de Win32. Cuando la aplicación termina, parece que Delphi hace una buena liberación en ambas circunstancias. Sin embargo, no es un buen estilo de programación hacer que el hilo sea forzado a finalizar sin ninguna referencia de lo que está haciendo en el momento, de modo que un archivo pueda quedar corrompido. Esta es la razón por la que es una buena idea tener una buena coordinación de la salida del hilo hijo desde el hilo principal de la VCL, aún cuando no haga falta transferir ningún dato entre los hilos: una salida limpia del hilo y el proceso es posible. En el capitulo 4 se discuten algunas soluciones a este problema.

Martin Harvey -