Aplicaciones Multihilo en Delphi. Multihilo y el BDE

Introducción

Uno de los problemas del multihilo en Delphi se da a la hora de acceder a la base de datos. En Delphi no podemos sencillamente ejecutar el siguiente código desde una tarea por que, probablemente, nos de un error en tiempo de ejecución

procedure MiAccesoABD;
var
  qry : TQuery;
begin
  qry.DatabaseName := 'MiBaseDeDatos';
  qry.Sql.Add('SELECT * FROM Productos');
  qry.Open;
  .
  .  // Más código
  .
  qry.Close;
  qry.Free;
end;

Sesiones de base de datos

Esto es debido a que el motor de base de datos (el BDE) no soporta llamadas concurrentes en la misma sesion de la base de datos. Esto quiere decir que, aunque podemos tener tan solo un TDatabase, debemos tener una sola sesion por cada hilo que acceda a la base de datos.

Una sesion encapsula una serie de información lógica de la base de datos tal como la conexión, los cursores abiertos, las queries ejecutadas (o preparadas) y las aisla de otras sesiones. A efectos prácticos una sesion se corresponde con una instancia de conexión a la base de datos por lo que, como es lógico, cada sesión creada y abierta consume toda una serie de recursos. A más sesiones más recursos consumidos.

La sesion por defecto de BDE

Cuando realizamos accesos a bases de datos en aplicaciones no multihilo, por ejemplo arrastrando un componente TDatabase y un componente TTable a nuestro formulario, aunque no creemos directamente ninguna sesión en realidad estamos usando una la sesión por defecto de BDE (BDE Default Session) que Delphi crea automáticamente una sesión que se utilizará en cada componente de base de datos si no se especifíca otra cosa.

Por que las sesiones no son Threadsafe

El hecho de que las sesiones de acceso a base de datos no sean seguras frente a multihilo responde, creo yo, a que sencillamente no se han implementado así, es decir, la gente de Borland simplemente diseño un objeto TSession en el cual toda secuencia de acceso a la base de datos (como en un TQuery o en un TTable) no iba a ser interrumpida por otro proceso. Desde un punto de vista teórico quizá podrían haber implementado un proceso de serialización de peticiones (es decir, ir encolando las peticiones que van llegando para no servirlas de forma concurrente e ir atendiendolas paso a paso) pero el caso es que estoy no se hizo de forma que seguimos teniendo nuestro problema.

Hay que tener en cuenta otra razón adicional. Cada objeto TSession representa una sesión en base de datos por lo que, desde un punto de vista conceptual, una sesión por hilo tiene mucho sentido ya que no tiene sentido que se use una sesión de base de datos simultaneamente desde dos "procesos" distintos.

Una sesión por hilo

Una de las soluciones más sencillas a la hora de evitar el problema consiste en crear una sesion por hilo de ejecución de forma que cada hilo tenga su propia sesión de acceso. Más adelante veremos que este sistema puede ser útil o no (por consumir demasiados recursos de forma innecesaria) en función del numero de tareas que tengamos.

Creando sesiones

Delphi proporciona un metodo muy sencillo de creación de sesiones en tiempo de ejecución mediante el uso de un singleton de tipo TSessionList llamado Sessions que disponde de diversos metodos para el manejado de sesiones. Asi podremos utilizar el metodo FindSession para obtener una determinada sesion dado su nombre y el metodo OpenSession para obtenerla (creandola si es necesario).

Limitando el numero de sesiones

Como he dicho antes, en ocasiones tener una sesión por hilo no es rentable, por un lado podemos descartar crearlas estáticamente si no sabemos cuantos hilos vamos a tener y si lo sabemos pero el numero de hilos va a ser muy numeroso probablemente no merezca la pena el gasto de recursos que supondría.

Por ejemplo, supongamos que tenemos una aplicación que sirve páginas web obtenidas de una base de datos. Dicha aplicación corre sobre una máquina multiprocesador con 4 procesadores en la que hemos determinado (por algún metodo) que es óptimo tener 6 hilos por procesador (por que queremos una alta disponibilidad y que el reparto de tiempos entre clientes sea lo más equitativo posible). En general esta es una situación muy común. Conforme van llegando peticiones se van sirviendo páginas a los clientes de forma concurrente.

En esta situación tenemos 24 sesiones sobre la base de datos, 24 sesiones que estarían consumiendo recursos. La necesidad de dichas sesiones puede variar dependiendo del caso. Si tenemos miles de clientes que producen 100 o 200 peticiones a la base de datos por segundo entonces es probable que nos hayamos quedado cortos, sin embargo si, por ejemplo, tenemos un sistema de cache que permite que tan solo se produzcan 10 o 20 accesos por segundo a la base de datos obviamente estamos usando más sesiones de las que necesitamos. El numero de tareas seguiría siendo correcto por que quizá tengamos 1000 clientes que necesitan ser atendidos con la mayor rapidez posible pero, de todas esas peticiones, realmente llegan muy pocas a la base de datos.

Una sesión para varios hilos

En realidad no es necesario que exista una sesión por hilo, la única restricción que tenemos es que un hilo utilice una sola sesion simultaneamente, es decir, es perfectamente válido que otro hilo "recoja" dicha sesión y la utilize cuando el otro haya dejado de usarla, es decir, mientras durante su uso, ningún hilo trata de hacer uso de ella.

De esta forma podemos implementar un mecanismo que permita a los hilos solicitar una sesión libre de entre un conjunto de sesiones al que podemos llamar Session Pool y que contendrá una serie de sesiones que estarán listas para ser usadas. Cuando un hilo necesite acceder a la base de datos intentaremos obtener una sesion del sesion pool, si no hay ninguna disponible pueden pasar dos cosas, que el hilo llamante tenga "prisa", es decir, que el acceso a base de datos esté marcado como preferente en cuyo caso crearemos una sesion "ex-professo" para el hilo que destruiremos al acabar o bien que no lo esté en cuyo caso bloquearemos el hilo hasta que haya una sesión disponible para asignarle.

El código del Session Pool

No voy a poner aqui todo el código del session pool puesto que lo adjunto con el árticulo pero si quiero comentar algunas "peculiaridades" del mismo.

En primer lugar el código no está totalmente completo, la funcionalidad de permitir que un hilo se salte la espera y cree una session en el momento sin tener que esperar (como comentaba un poco más arriba) queda como ejercicio para el lector aunque teniendo la base hecha no debería ser demasiado dificil inferir lo demás.

El Session Pool esta implementado siguiendo un patron Singleton de forma que el objeto session_manager que se encarga de atender a las peticiones de sesion por parte de los hilos, este siempre disponible.

Los objetos TBaseQuery y TOnDemandQuery derivan de TQuery y adquieren sesiones automáticamente de forma que el proceso es transparente para el usuario de dichos objetos, sin embargo el uso del session_manager no es exclusivo de dichas clases, esto es, un hilo podría perfectamente obtener una session a través del session_manager y asignarla a un TQuery propio de forma manual.

AdjuntoTamaño
USessionPool.pas9.48 KB
7
Average: 7 (3 votes)
Your rating: None