[2024r3] Nuevos Hilos para tus Apps: de Cooperativos a Preemtivos

A continuación encontrarás traducido al castellano el artículo escrito por William Yu y publicado originalmente en el Blog Oficial de Xojo.

La última actualización de Xojo introduce un nuevo tipo de hilo que maximiza el potencial de tus equipos multi-núcleos.

Este nuevo modelo de hilo preemptivo mejora la eficiencia y tiempos de respuesta, permitiendo una multitarea y gestión de recursos mejorados al tiempo que proporciona una significativas mejoras de rendimiento.

En este artículo exploraremos las ventajas de esta actualización, además de adentrarnos en algunos detalles sobre la implementación y guiarte sobre cómo puedes utilizar con todo su potencial las nuevas capacidades de hilos preemptivos.

Modelos de Hilos

  • Hilos Cooperativos: Este es el modelo de hilos soportado tradicionalmente por Xojo y que se continúa ofreciendo. En este modelo los hilos deben de proporcionar control de forma voluntaria en los puntos que definimos. Esto asegura que la mayoría de las operaciones en el código de Xojo sean seguras en su ejecución multi-hilo, dado que gestionamos el acceso a los bloques de memoria, Arrays, etc. Sin embargo, esta aproximación puede derivar en ciertas ineficiencias si un hilo falla a la hora de devolver el control, bloqueando potencialmente la UI y, dado que sólo se puede ejecutar un hilo a la vez, no utiliza por completo todos los núcleos del procesador y tampoco tiene la capacidad de ser ejecutado en paralelo.
  • Hilos Preemtivos: Introducidos en Xojo 2024r3, ese nuevo tipo de hilo permite que puedan ser interrumpidos en cualquier momento, mejorando la respuesta y utilizando por completo todos los núcleos disponibles. Si bien este modelo proporciona mejoras de rendimiento significativas, también introduce una serie de retos. Dado que los hilos pueden interrumpirse en cualquier punto, tendrás que ser más diligente a la hora de bloquear los recursos, como puedan ser los bloques de memoria o los Array, para evitar así las potenciales condiciones de carrera.

Uso de Hilos Preemtivos

Hemos añadido la nueva propiedad Thread.Type que te permite conmutar los hilos al nuevo modelo de hilo preemptivo. Así es como puedes configurar uno:

Thread1.Type = Threads.Types.Preemptive
Thread1.Start

Puedes definir esta propiedad tanto antes de iniciar el hilo o bien cuando el hilo ya se está ejecutando. Sin embargo, si se cambia cuando el hilo está activo esto ha de realizarse de forma segura desde el hilo en ejecución propiamente dicho, y no de forma externa.

Event Window1.Opening
  // Inicia el hilo y configura el tipo en el evento Run
  Thread1.Start
End Event
 
Sub Thread1.Run
  Me.Type = Threads.Types.Preemptive
  // Ejecuta un proceso muy largo de forma preemptiva.
  newMemBlock = memBlock.Compress
  
  Me.Type = Threads.Types.Cooperative
  // Cambia al modo cooperativo y modifica un array de forma segura.
  mySharedArray.Add("New item")
End Sub

Concurrencia y Sincronización

Como se ha mencionado anteriormente, los hilos cooperativos evitan muchos de los problemas que pueden tener lugar cuando se usan los hilos preemptivos, dado que controlamos cuando tiene lugar el cambio entre hilos. Con los hilos preemptivos, sin embargo, no puedes modificar las cosas de forma segura sino que tendrás que utilizar algunas herramientas como CriticalSection y los Semáforos con más frecuencia para proteger los recursos compartidos.

El reto consiste en soportar de forma eficiente ambos modelos de hilos al tiempo que el hilo principal de ejecución se sincroniza con cualquiera de ellos. Para lograrlo hemos añadido la nueva propiedad Type a CcriticalSection y Semaphore, los cuales deben corresponderse con el tipo de hilo que estés utilizando a la hora de proteger los recursos compartidos.

// Imagina el hilo principal y dos hilos adicionales, todos ellos intentando acceder de forma simultánea a un diccionario.
Sub RunTest
  Thread1.Type = Thread.Types.Preemptive
  Thread2.Type = Thread.Types.Preemptive
 
  CSLock = New CriticalSection
  // Dado que estamos utilizando esta CriticalSection en estos hilos preemptivos
  // el tipo de sincronización ha de ser compatible.
  CSLock.Type = Thread1.Type
 
  Thread1.Start
  Thread2.Start
 
  Do
    CSLock.Enter
    MyDictionary.RemoveAll
    CSLock.Leave
  Loop
End Sub
 
Sub Thread1.Run
  Do
    CSLock.Enter
    MyDictionary.Value("Random1") = Rnd
    CSLock.Leave
  Loop
End Sub
 
Sub Thread2.Run
  Do
    CSLock.Enter
    MyDictionary.Value("Random2") = Rnd
    CSLock.Leave
  Loop
End Sub

¿Qué cosas son seguras en ejecución multi-hilo?

Hemos actualizado nuestro framework para salvaguardar las áreas que anteriormente no eran seguras en su ejecución de hilos preemptivos. En su mayo parte, prácticamente todo nuestro framework es ahora seguro en lo que respecta al uso de hilos preemptivos, salvo algunas excepciones como XojoScript y Runtime.IterateObjects. Cuando se comparten recursos como MemoryBlocks, Diccionarios o Arrays entre hilos es probable que debas de utilizar CriticalSection o Semaphore para garantizar un acceso seguro. Sin embargo no se requiere la sincronización, si sólo estás leyendo de estos recursos sin realizar ninguna modificación. Te gustará saber que las llamadas a Thread.AddUserInterfaceUpdate desde un hilo preemptivo también es seguro.

Consideraciones de Rendimiento

Con la capacidad de cambiar tus hilos a una ejecución preemptiva existen algunos factores de rendimiento a tener en cuenta. Si bien puede parecer apetecible la ejecución de 100 hilos de forma simultánea, por lo general resulta en una sobrecarga de los recursos del sistema. Para una eficiencia óptima es mejor limitar la cantidad de hilos al número de núcleos disponibles. Para ayudarte con ello, hemos introducido la propiedad System.CoreCount, la cual te permitirá determinar la cantidad de núcleos en tu sistema.

Si bien nuestro framework no te protege de todos los potenciales problemas, gestionamos la sincronización en operaciones clave como la creación y destrucción de objetos. Sin embargo, es probable que adviertas que estas acciones son más lentas en los hilos preemptivos debido a la sincronización adicional requerida.

Uso y Ejemplos

No hay duda de que los hilos preemptivos ofrecen ventajas significativas, y que cuando se utilizan de forma correcta, pueden mejorar en gran medida el rendimiento de tus apps. Chequea los proyectos de ejemplo incluidos con la release 2024r3 para ver los hilos preemptivos en acción, ¡y disfruta utilizando estos nuevos hilos en tus propias apps!

¡Un agradecimiento especial a nuestros MVP por su inestimable ayuda a la hora de refinar y probar esta nueva característica! Asegúrate de probar el nuevo ejemplo ThreadPool para ver un caso de uso práctico del mundo real.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *