1.1 Historia
La humanidad a menudo llega a la idea de construir un nuevo proyecto grandioso que eclipsará a todos los proyectos grandiosos anteriores. La Pirámide de Keops en Giza es la más grande, el Burj Khalifa en Dubái es el más alto, y la Gran Muralla China es la más larga.
Sin embargo, organizar el trabajo en tales proyectos es muy difícil. Si construyes una nueva pirámide el doble de alta, requerirá ocho veces más piedra. Eso significa que necesitas o aumentar la productividad de las canteras o abrir más canteras.
También será necesario encontrar ocho veces más ingenieros, quizás abrir escuelas de ingeniería, estandarizar dibujos, geometría, escritura. En resumen, un gran desafío...
En los miles de años que han pasado desde la construcción de las pirámides, nada ha cambiado: la gente sigue pensando en cómo realizar más trabajo en menos tiempo. Y cuando se inventaron las computadoras, las mejores mentes de la humanidad comenzaron a abordar este problema.
¿Cómo ejecutar un programa diez veces más rápido? Parecería una pregunta extraña: si el procesador ya funciona a máxima velocidad, entonces, probablemente, de ninguna manera. Sin embargo, no mencioné en vano a las mejores mentes de la humanidad. Ellos analizaron el trabajo de todos los programas y llegaron a la conclusión de que hay tres grandes áreas para el crecimiento.
Eliminación de inactividad
Resulta que la mayor parte del tiempo el programa está inactivo. Siempre espera algo: los datos deben copiarse de un lugar de la memoria a otro, cargarse desde el disco duro en la memoria, esperar la respuesta del servidor a una solicitud, entrada de datos del usuario, etc.
Todas estas tareas no las realiza el procesador central, sino los controladores de memoria, disco, etc. Y el procesador central mientras tanto podría ser usado para algo útil. Así surgió la idea de ejecutar en un procesador no un hilo de ejecución de comandos (thread), sino varios.
Y mientras, por ejemplo, un hilo espera la entrada de datos del usuario, el segundo descarga algo de la red, el tercero procesa datos, el cuarto dibuja imágenes en la pantalla. Luego esta tarea evoluciona en tareas asincrónicas y coroutines, pero de eso hablaremos más tarde.
Más programas
Si los programas están inactivos el 80% del tiempo, entonces esto, por supuesto, no es bueno. Por otro lado, no se pueden reescribir todos los programas bajo nuestros nuevos enfoques. Tal vez este problema se pueda resolver de otra manera: simplemente se pueden ejecutar varios programas al mismo tiempo en la computadora.
En este caso, el sistema operativo supervisa el trabajo de los programas, y si un programa está inactivo, cede su tiempo de ejecución a otro programa. Tal cambio ocurre decenas de veces por segundo, y el usuario simplemente no nota los cambios: desde su perspectiva, los programas se ejecutan al mismo tiempo.
Más procesadores
La ejecución simultánea de programas es genial, pero ¿qué pasa si hay muchos programas, y están inactivos poco tiempo? Si no hay inactividad, no hay uso efectivo. Por ejemplo, tenemos diez programas que calculan algo, o un juego que consume muchos recursos y algo más junto con él.
La solución a este problema fue la adición de varios procesadores en un procesador. Para evitar confusiones, se empezaron a llamar núcleos. Ahora tenemos procesadores que tienen varios núcleos (subprocesadores), en los que trabajan en paralelo decenas de programas.
¡Dato curioso! Número de núcleos en procesadores
Actualmente, los procesadores de servidor pueden tener de 64 a 256 núcleos, en algunos casos especializados incluso más. Por ejemplo, los procesadores AMD EPYC de 4ª generación ofrecen hasta 96 núcleos, y el IBM POWER10 puede tener hasta 240 núcleos por chip. Los procesadores de usuario también han evolucionado significativamente: los CPU de escritorio de alto rendimiento, como el AMD Threadripper, pueden tener hasta 64 núcleos, mientras que los modelos más comunes normalmente tienen de 6 a 16 núcleos.
¿Hacia dónde vamos a continuación? ¡Hay hacia dónde! Primero, en una placa madre de servidor se pueden instalar varios procesadores, por ejemplo dos o incluso cuatro. En segundo lugar, los servidores se pueden combinar en racks de servidor de 10-20 piezas. Y los racks de servidor forman parte de centros de datos, donde hay miles de racks.
Los sistemas distribuidos y las arquitecturas de microservicios se han convertido en prácticas comunes en el desarrollo moderno de software. Muchas grandes empresas, como Google, Amazon, Netflix, e incluso empresas más pequeñas, utilizan sistemas distribuidos para procesar grandes volúmenes de datos, garantizar alta disponibilidad y escalabilidad en sus servicios. Estos sistemas permiten utilizar eficientemente los recursos de muchos servidores trabajando juntos como una sola unidad, lo que mejora significativamente el rendimiento y la tolerancia a fallos de las aplicaciones.
1.2 Beneficios
La concurrencia, o la ejecución simultánea de tareas dentro de un programa, ofrece varios beneficios clave que pueden mejorar significativamente el rendimiento y la eficiencia del software. Vamos a ver los principales beneficios de la concurrencia.
1. Aumento del rendimiento y velocidad de ejecución
Ejecución paralela de tareas: La concurrencia permite ejecutar varias tareas al mismo tiempo, lo cual es especialmente útil para programas que requieren ejecutar muchas operaciones independientes. Esto permite acelerar la ejecución del programa, ya que las tareas se ejecutan en paralelo, utilizando todos los recursos disponibles del procesador.
Uso de procesadores multinúcleo: Los procesadores modernos tienen varios núcleos, y la concurrencia permite utilizar por completo su potencia, distribuyendo los threads por diferentes núcleos para ejecutar tareas en paralelo.
2. Mejora de la capacidad de respuesta e interacción con el usuario
Tareas en segundo plano: La concurrencia permite realizar operaciones largas o que consumen muchos recursos en segundo plano, manteniendo aún así el hilo principal, responsable de la interfaz de usuario, con buena capacidad de respuesta. Esto mejora la experiencia del usuario, ya que la interfaz no se bloquea al realizar tareas pesadas.
Operaciones asincrónicas: La interacción con el usuario y la gestión de eventos pueden ocurrir en paralelo con la ejecución de las tareas principales, lo que hace las aplicaciones más responsivas y eficientes.
3. Uso eficiente de los recursos del sistema
Optimización de recursos: La concurrencia permite usar más eficientemente los recursos del sistema, como el tiempo del procesador y la memoria. Esto es especialmente importante para servidores y sistemas de cálculo de alto rendimiento, donde la concurrencia permite manejar una gran cantidad de solicitudes y tareas al mismo tiempo.
Gestión de entrada/salida: Las aplicaciones concurrentes pueden gestionar tareas de entrada/salida (por ejemplo, operaciones de red, lectura y escritura de archivos) más efectivamente, ya que los threads pueden realizar otras tareas mientras uno de ellos espera que se complete una operación de entrada/salida.
4. Soporte para multitarea
Multitarea: La concurrencia permite ejecutar varias tareas al mismo tiempo dentro de un solo proceso, lo que simplifica el desarrollo de aplicaciones que requieren multitarea, como servidores web, bases de datos y aplicaciones en tiempo real.
Procesamiento paralelo de datos: En tareas relacionadas con el procesamiento de grandes volúmenes de datos, la concurrencia permite dividir los datos en partes y procesarlas en paralelo, lo que acelera significativamente la ejecución de la tarea.
Ejemplos de uso de la concurrencia
Servidores web: La concurrencia permite a los servidores web manejar múltiples solicitudes de clientes al mismo tiempo, aumentando el rendimiento y la escalabilidad de los servidores.
Interfaces gráficas de usuario (GUI): En aplicaciones con interfaz gráfica, la concurrencia permite realizar cálculos largos en segundo plano, manteniendo la interfaz con buena capacidad de respuesta.
Cálculos científicos: La concurrencia se utiliza para el procesamiento paralelo de datos en cálculos científicos, lo que permite acelerar significativamente la ejecución de tareas computacionales complejas.
Juegos y simulaciones: En los juegos, la concurrencia permite procesar simultáneamente la física, los gráficos, los sonidos y las acciones del usuario, mejorando el rendimiento y el realismo.
1.3 El nombre correcto
Hay algunas críticas al nombre "concurrencia". Está compuesto de dos palabras: "multi" y "thread", insinuando que dentro del programa hay muchos "threads de ejecución de comandos" que hacen algo.
Es una analogía bonita, pero en la literatura (original) en inglés,
para denotar varias acciones realizadas en paralelo, se utiliza el término
"thread". Y, en consecuencia, concurrencia se llama
multi-threading
.
Esto podría considerarse un malentendido menor: ¿a quién le importa cómo
se traducen diferentes términos a otro idioma si no fuera porque en programación se
empezó a utilizar activamente algo conocido como
stream
, que no se puede traducir de otra forma que no sea "flujo".
Por lo tanto, ahora en la terminología hispanohablante hay cierta confusión, que se resuelve de dos maneras:
-
Thread
(hilo) se traduce como "hilo de ejecución [de comandos]". Stream
(flujo) se traduce como "flujo de datos".
Por otro lado, muchos programadores simplemente comenzaron a usar términos en inglés sin traducirlos:
-
Thread
(hilo) se pronuncia como "thread", y multi-threading como "multi-threading". Stream
(flujo) se pronuncia como "stream".
A menudo se llama a Thread hilo, pero el término "multi-hilos" no se ha afianzado. Por eso a menudo en las conversaciones se usa "hilo" y "concurrencia" al mismo tiempo.
El uso de muchos términos prestados enriquece el lenguaje, permite llenar las palabras con un nuevo significado y facilita la comunicación con colegas de otros países. Estoy completamente a favor de este enfoque.
GO TO FULL VERSION