Uno de los retos más importantes a los que se enfrenta en breve el desarrollo de aplicaciones tiene que ver con la programación paralela. Ahora que se empieza a vislumbrar el acercamiento del fin de la Ley de Moore, si queremos seguir el espectacular aumento de potencia debemos irnos a entornos multi-procesador o multi-core. Hace unos años eran coto reservado a entornos de investigación, y ahora ya están encima de nuestra mesa…
Hay varias visiones sobre como se debe atacar este problema, cual debe ser el rol del desarrollador, de los frameworks y del sistema operativo para asegurar una buena productividad y a la vez ser capaz de asegurar un óptimo consumo de los distintos recursos. Como dijo Rodrigo una vez… La Broma ha terminado!
La versión 4.0 del framework trae novedades al respecto, en concreto las famosas Parallel Extensions (que también pueden descargarse para funcionar con el framework 3.5), que permiten un nivel de abstracción más alto y facilitan el uso de la programación concurrente. Se ha hablado largo y tendido en geeks sobre las parallel.
Por otro lado, hace ya bastante tiempo que Microsoft tiene el DevLabs, una especie de laboratorio de investigación de donde salen proyectos de investigación que en un futuro pueden ver la luz (como productos independientes o bien integrándose en otros como puede ser .NET Framework). Del DevLabs han salido cosas tan interesantes como Code Contracts (de las que ya he hablado en este mismo blog), Pex, Small Basic (una versión de Basic para aprender a programar) y lo que os quiero comentar en este post: STM.NET.
¿Que es STM.NET?
STM.NET son unas extensiones sobre el framework 4.0 que implementa el concepto de Software Transactional Memory, un mecanismo que nos permite aislar zonas de código para que se ejecuten de forma segura en un entorno multiprocesador. STM.NET no ofrece nuevas abstracciones sobre tareas concurrentes (como hacen las Parallel) sinó que se centra en como proteger las zonas de código compartido. Evidentemente esto no es nuevo, .NET ya ofrece un mecanismo para la protección de código compartido: los locks y toda una pléyade de clases para la sincronización de threads.
¿Entonces para que STM? STM ataca el problema desde otro punto de vista: usando el concepto de transacciones. En concreto de las propiedades ACID (atomicidad – atomicy, coherencia – consistency, aislamiento – isolation y permanencia – durability), STM usa dos: La atomicidad y el aislamiento.
Así, del mismo modo que se definen transacciones de bases de datos, podemos definir transacciones de código de tal manera que sean atomicas (o se ejecuta todo el código o no se ejecuta nada) y aisladas (si se ejecutan varias transacciones de forma concurrente sus efectos serán como si se ejecutaran una tras otra).
Instalación de STM.NET
Para instalar STM.NET se requiere Visual Studio 2008 y no se soporta la beta de Visual Studio 2010: si instalas STM.NET en una máquina con Visual Studio 2010 vas a tener problemas. Desde la página web de STM.NET puede instalarse una versión modificada del .NET Framework 4.0 (que se instala con la versión 4.0.20506) y el adaptador para Visual Studio 2008, que nos permite crear aplicaciones de consola usando STM.NET. Una vez instalados ambos estamos listos para empezar :)
El primer ejemplo…
Vamos a empezar con un trozo simple de código donde dos threads van a acceder a la misma variable compartida:
static int sharedValue = 0;
static void Main(string[] args)
{
Thread t1 = new Thread(ThreadProc);
Thread t2 = new Thread(ThreadProc);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.WriteLine("sharedValue es {0}", sharedValue);
Console.ReadLine();
}
static void ThreadProc()
{
for (int i = 0; i < 100; i++)
{
sharedValue++;
Thread.Sleep(10);
}
}
Si ejecutais este programa la salida debería ser 200, ya que los dos threads incrementan cada uno 100 veces la variable. Pero como sabemos, threads accediendo a datos compartidos… es sinónimo de problemas. Actualmente para solucionar este problema podríamos usar un lock, pero STM.NET nos da otro mecanismo: crear una transacción para acceder a la variable sharedValue. Actualmente para crear una transacción se usa el método Do, de la clase Atomic y se le pasa un delegate con el método que tiene el código de la transacción.
Así modificamos ThreadProc para que quede:
static void ThreadProc()
{
for (int i = 0; i < 100; i++)
{
Atomic.Do(() => sharedValue++);
Thread.Sleep(10);
}
}
Con esto todo el código que hemos metido dentro del Atomic.Do (en nuestro caso el incremento de la variable) se ejecutará dentro de una transacción, y por el principio de aislamiento, si dos (o más) transacciones se ejecutan a la vez, sus efectos serán los mismos que si se ejecutasen una tras otra. En efecto, ahora al finalizar el programa sharedValue siempre vale 200.
Este es el ejemplo más sencillo: como imitar un lock usando STM.
El segundo ejemplo…
Veamos ahora la propiedad de atomicidad, y como Atomic.Do() no es lo mismo que usar un lock de C#.
Si tenemos el siguiente código:
int _x = 10;
int _y = 20;
try
{
Atomic.Do(() =>
{
_x++;
_y--;
throw new Exception();
});
}
catch (Exception)
{
Console.WriteLine("_x = {0}, _y = {1}", _x, _y);
}
Cual es el valor de las variables dentro del catch? Pues _x seguirá teniendo el valor de 10 e _y seguirá teniendo el valor de 20. Al lanzarse una excepción dentro de la transacción se realiza un rollback, de forma que las variables recuperan el valor original que tenian antes de entrar en la transacción (principio de atomicidad).
Resumiendo…
Este post ha sido una muy breve introducción a STM.NET. Obviamente se han quedado cosas en el tintero, pero sólo queria mostraros un poco algunas de las ideas que se están cociendo en los DevLabs… El tiempo dirá si estas ideas se terminan consolidando en alguna versión futura del Framework (como Code Contracts) o se quedan en el olvido…
Saludos!
PD: Este es un crosspost desde mi blog de geeks.ms!
No hay comentarios:
Publicar un comentario