El presente artículo tiene el objetivo de mostrar los principios básicos de la programación multi-hilo usando el Framework Qt.
Los hilos permiten hacer que los sistemas ejecuten tareas en paralelo, al igual que procesos. Entonces, ¿cómo se diferencian los hilos de los procesos? Mientras que usted está haciendo los cálculos en una hoja de cálculo, puede haber también un reproductor multimedia que se ejecuta en el mismo escritorio tocando su canción favorita. Aquí hay un ejemplo de dos procesos trabajando en paralelo: uno que ejecuta el programa de hoja de cálculo, una ejecución de un reproductor de música. La multitarea es un término muy conocido para esto. Una mirada más cercana al reproductor de música revela que allí son cosas que suceden en paralelo dentro de un único proceso. Mientras reproductor de música realiza el envío de la música para el controlador de audio, la interfaz de usuario con todas sus campanas y silbatos se actualiza constantemente. Para esto son los hilos - la concurrencia dentro de un único proceso [1].
Entonces, ¿cómo se implementa la concurrencia? Trabajo paralelo en las CPU de un solo núcleo, es una ilusión que es algo similar a la ilusión de imágenes en movimiento en el cine. Para los procesos, la ilusión se produce mediante la interrupción de trabajo del procesador en un proceso después de un tiempo muy corto. A continuación, el procesador se mueve al siguiente proceso. Para cambiar entre procesos, el contador de programa actual se guarda y contador de programa del próximo procesador se carga. Esto no es suficiente, ya que el mismo hay que hacer con los registros y cierta arquitectura y datos específicos del sistema operativo.
Así como una CPU puede alimentar dos o más procesos, también es posible dejar que la CPU ejecute en dos segmentos de código diferentes de un único proceso. Cuando se inicia un proceso, siempre se ejecuta un segmento de código y por lo tanto, se dice que el proceso ejecuta un hilo. Sin embargo, el programa puede optar por iniciar un segundo hilo. Entonces, dos secuencias de códigos diferentes se procesan simultáneamente dentro de un solo proceso. La concurrencia se logra en las CPU de un solo núcleo guardando repetidamente contadores de programa y luego los registros cargan contadores y registros del programa del próximo hilo. No se requiere la cooperación del programa para desplazarse entre los hilos activos. Un hilo puede estar en cualquier estado cuando se produce el cambio al siguiente contexto.
La tendencia actual en el diseño de la CPU es tener varios núcleos. Una aplicación de un único subproceso típico puede hacer uso de un solo núcleo. Sin embargo, un programa con múltiples hilos se puede asignar a varios núcleos, lo que hace que las cosas sucedan de una manera verdaderamente concurrente. Como resultado, la distribución del trabajo a más de un hilo puede hacer que un programa funcione mucho más rápido en las CPU multinúcleo debido a que núcleos adicionales pueden ser utilizados.
Hilo GUI e Hilo Trabajador
Como se mencionó, cada programa tiene un hilo cuando se inicia. Este hilo se llama el "hilo conductor" (también conocido como el "hilo GUI" en aplicaciones Qt). La interfaz gráfica de usuario Qt se debe ejecutar en este hilo. Todos los widgets y varias clases relacionadas, por ejemplo QPixmap, no trabajan en los hilos secundarios. Un subproceso secundario que comúnmente se conoce como un "subproceso de trabajo" ya que se utiliza para descargar el trabajo de procesamiento del hilo principal.
El acceso simultáneo a los datos
Cada hilo tiene su propia pila, lo que significa que cada hilo tiene su propio historial de llamadas y variables locales. A diferencia de los procesos, los hilos comparten el mismo espacio de direcciones. El siguiente diagrama muestra cómo se encuentran los bloques de construcción de los hilos en la memoria. Contador y los registros de hilos inactivos Programa suelen mantenerse en el espacio del núcleo. Hay una copia compartida del código y una pila separada para cada hilo.
Si dos hilos tienen un puntero al mismo objeto, es posible que los dos hilos tengan acceso a dicho objeto al mismo tiempo y esto potencialmente puede destruir la integridad del objeto. Es fácil imaginar las muchas cosas que pueden salir mal cuando dos métodos del mismo objeto se ejecutan simultáneamente.
A veces es necesario para acceder a un objeto a partir de diferentes hilos, por ejemplo, cuando los objetos que viven en diferentes hilos necesitan comunicarse. Desde hilos utilizan el mismo espacio de direcciones, es más fácil y más rápido para hilos para intercambiar datos de lo que es para los procesos. Los datos no tiene que ser serializado y copiado. Pasar punteros es posible, pero debe haber una coordinación estricta de lo que toques de hilo que se oponen. La ejecución simultánea de las operaciones en un objeto debe ser prevenida. Hay varias maneras de lograr esto y algunas de ellos se describen a continuación.
Entonces, ¿Cómo se pueden programar estas aplicaciones de manera segura? Todos los objetos creados en un hilo se pueden utilizar de manera segura dentro de ese hilo, siempre que otros hilos no tengan referencias a los mismos y los objetos no tienen acoplamiento implícito con otros hilos. Tal acoplamiento implícito puede suceder cuando los datos son compartidos entre instancias como miembros estáticos, singletons o datos globales. Debe familiarizarse con el concepto de clase hilo y funciones seguras y de reentrada.
Uso de hilos
Básicamente, existen dos escenarios fundamentales para el uso de los hilos:
- Hacer un procesamiento más rápido, haciendo uso de los procesadores multinúcleo.
- Mantener el hilo GUI u otros hilos críticos liberados al descargar el procesamiento de larga duración o el bloqueo de llamadas a otros hilos.
¿Qué Tecnología Qt se debe usar para el manejo de hilos?
A veces quieres hacer algo más que la ejecución de un método en el contexto de otro hilo. Es posible que desee tener un objeto que vive en otro hilo que proporciona un servicio al hilo GUI. Tal vez usted quiere otro hilo que siga con vida para siempre para sondear los puertos de hardware y enviar una señal al hilo GUI cuando algo notable ha sucedido. Qt proporciona diferentes soluciones para el desarrollo de aplicaciones con subprocesos. La solución correcta depende de la finalidad del nuevo hilo, así como el tiempo de vida que se requiere de ese hilo:
Vida útil del hilo | Tarea de Desarrollo | Solución |
Una llamada | Ejecutar un método dentro de otro hilo y dejar el hilo cuando termine el método. | Qt proporciona diferentes soluciones: Escribir una función y ejecutarla con QtConcurrent::run() Derivar una clase de QRunnable y ejecutarla en el hilo global con QThreadPool::globalInstance()->start() Derivar una clase de QThread, reimplementar el método QThread::run() y utilizar QThread::start() para ejecutarlo. |
Una llamada | Operaciones se han de realizar en todos los hilos de un contenedor. El procesamiento deberá ser realizado utilizando todos los núcleos disponibles. Un ejemplo común es la producción de imágenes en miniatura de una lista de imágenes. | QtConcurrent proporciona la función de mapa () para aplicar las operaciones en cada elemento contenedor, filtro () para la selección de elementos de recipiente, y la opción de especificar una función de reducir para combinar los elementos restantes. |
Una llamada | Una operación de larga duración tiene que ser puesto en otro hilo. Durante el curso de procesamiento, información de estado se debe enviar al hilo de la interfaz gráfica de usuario. | Utilice QThread, reimplementar run() y también las signal/slot sean necesarias. Conecte las señales a las ranuras (slots) del hilo GUI utilizando conexiones de señal / ranura (signal/slot) en cola. |
Permanente | Tener un objeto de estar en otro hilo y dejar que se realicen diferentes tareas bajo petición. Esto significa que la comunicación hacia y desde el subproceso de trabajo que se requiere. | Derivar una clase de QObject e implementar las ranuras y las señales necesarias, mover el objeto a un hilo con un bucle de eventos de correr y comunicarse con el objeto a través de conexiones de señal / ranura en cola. |
Permanente | Tener un objeto que vive en otro hilo, dejar al objeto realizar tareas repetitivas, como el sondeo de puertos y permitir la comunicación con el hilo GUI. | Igual que el anterior, pero también utilizar un temporizador en el subproceso de trabajo para implementar la encuesta. Sin embargo, la mejor solución para la encuesta es evitarla por completo. A veces, usar QSocketNotifier es una alternativa. |
En próximas entradas estaremos profundizando sobre otros temas de programación concurrente usando el framework Qt.