Hablemos de GO y la Concurrencia

En este episodio de estilo vlog‑podcast, Violeta, Hexa, Chip y Pro discuten en profundidad la concurrencia en el lenguaje de programación Go. Comienzan recordando la promesa de compatibilidad de Go 1 (código escrito para 1.0 sigue compilando en 1.25) y la historia de sus creadores (Rob Pike, Ken Thompson, Robert Griesemer). Luego explican la diferencia entre paralelismo y concurrencia, introduciendo el modelo de Go: muchas gorutinas (gorrutinas) ejecutándose sobre pocos hilos del sistema operativo, gestionado por el runtime mediante un programador de tareas abstracto (visualizado como un toroide). Se describen cómo los sistemas operativos (Linux, BSD, etc.) proveen hilos y procesos, y cómo Go abstrae esas diferencias sin necesidad de una máquina virtual, compilando a binario nativo para cada SO. Se profundiza en los canales tipados, su uso para comunicación segura entre gorutinas y la detección de problemas de concurrencia (condiciones de carrera, deadlocks, fugas de memoria). Además, se comparan Go con otros lenguajes (Python, Java, Rust, Erlang/Elixir) resaltando ventajas de Go en despliegue (binario único), curva de aprendizaje y escalabilidad vertical (miles‑millones de gorutinas en una máquina) y horizontal (clústers). Se menciona la evolución del liderazgo del proyecto (Russ Cox hasta 2024) y la percepción de la comunidad, incluyendo humor con las mascotas Byte y Slice. El episodio concluye invitando a la audiencia a probar gorutinas y canales, y a comentar sobre sus experiencias.
Chapters
¡Volvemos, chicos! ¡Cómo me alegra que nuestros seguidores sean tan afines a Go!
Violeta, ¿con quién hablas?
Ah, ¿tú no conoces a Byte y a Slice, los goffers?
No sabía que era el día de traerse a la mascota al trabajo.
¡No somos mascotas! En verdad sí. ¡Pero no me gusta cómo lo dice él!
Ya veo que os habéis preparado para el vídeo de hoy. Violeta, ¿qué tienes pensado?
En el vídeo anterior hablamos de nombre, su origen y sus creadores. ¡Proverbios! ¡Cierto! La filosofía. Y lo dejamos en la concurrente y la promesa de no cambiar nada.
Mientras sí vayan mejorando y optimizando, no está mal.
Pues comencemos.
Sí, sí, pero antes voy a tener unas palabras con la audiencia.
Sé amable, son nuestros seguidores, nuestros historiadores y cronistas.
Muchos de ellos no, y hay que darles caña. Sí, vosotros. ¿Creéis que lo que hacéis es óptimo?
¿A qué te refieres?
A que no presionen el botón de suscribirse, no presionen el botón de me gusta y no se unan al canal.
Es verdad que si se unen nos ayudan, pero Hexa, no hay que ser borde.
¡Tonterías! Ya le estáis dando al botón de unirse. Y como cronistas, que no tengan que volver a hacer el recordatorio.
Es nueva. Vosotros simplemente, si podéis, pues dadle al botón de unirse, y si no estáis suscritos, pues también. Los me gusta siempre se agradecen. ¿Seguimos? La promesa de la continuidad.
¿No tenéis la impresión de una velocidad vertiginosa y cambios, cambios y más cambios que hacen cambiar todo el código en menos de un año en ciertos lenguajes?
¿Como en JavaScript?
Parece que los 3 mosqueteros de Google también pensaron en ello, y la gran promesa de Go 1 es que si escribes un código en Go para una versión de Go 1, incluso si fue la 1.0, puedas compilarla en la actual 1.25 y funciona sin problemas.
¿Quiénes son los 3 mosqueteros de Google?
Ah, perdona, que no todos estamos en el club. Me refiero al gran Kent Thomas, Johnson, Rob Pike y Robert Griesemer.
Pero seguro que todo funciona.
Esa es la promesa, pero obviamente no es extensible a paquetes fuera del núcleo de Go. Sin embargo, el núcleo de Go tiene de todo.
¿Ves? Ya no es todo, pero se agradece el intento. En Python, dentro de nuestras posibilidades, manteniéndote en la versión 3, también suele funcionar todo muy bien.
¿También sucede en Java?
En Java no, pro. Si intentas compilar algo de hace 12 años seguro que ya no compila.
Es posible, depende del proyecto, pero sí que sigue ejecutándose el fichero JAR que se haya generado. El bytecode mantiene una retrocompatibilidad.
Pues eso imagínatelo a nivel de código.
Me lo imagino y no me gusta pensar que no haya avance en muchos años.
¿Cuándo fue lanzado Go 1?
El 28 de marzo de 2012.
Uno no puede solo escribir código en 2012 y esperar que funcione en 2025.
Pero lo hace.
Empieza a convencerme este lenguaje para hacer proyectos.
¿A que sí?
Creo que no nos estamos enfocando en lo primordial de este capítulo.
¡Cierto! Perdona, Hexa, vamos al punto principal. ¿Por qué Go es el mejor lenguaje para concurrencia?
Esto vas a tener que defenderlo bien. Tengo bastantes lenguajes en mente que compiten y pueden ganar contra Go.
Comencemos por el principio. En un capítulo comentamos la tendencia a realizar paralelismo en lugar de crecer a base de procesadores más veloces utilizando simplemente concurrencia.
Exacto, una cosa es paralelismo y otra concurrencia, como nos decía Rob Pike. Como decíamos en ese capítulo, el paralelismo es cuando dos tareas se ejecutan en paralelo en procesadores o núcleos diferentes, mientras que la concurrencia ocurre siempre. Incluso cuando hay únicamente un núcleo.
Y si hay solo un núcleo, ¿cómo conseguimos que se ejecuten dos tareas?
Gracias al programador de tareas. Todos los sistemas operativos actuales tienen uno y la forma de organizar las tareas o procesos difiere de uno a otro.
Totalmente. La esencia se mantiene, pero el mejor en la gestión es Linux.
¿Linux? Yo te veía más de BSD o incluso ese hecho en Rust. ¿Cómo se llamaba?
Redox. Sí, pero no. El sistema de kernel modular monolítico de Linux no tiene rival en rendimiento. BSD es también monolítico, pero con una arquitectura más conservadora, y el resto son microkernels con lo que ello conlleva, es decir, más seguridad en ciertos aspectos pero un rendimiento muy inferior.
Lo importante es visualizar al programador de tareas como un toroide.
¿Eso no es un donut?
No, en geometría esa forma se llama toroide. Lo importante es que si estamos ejecutando 3 tareas con la misma prioridad, el toroide es como un reloj, asigna cada sección del toroide teniendo en este caso 3 secciones iguales y cada sección tiene un tiempo asignado de ejecución dependiendo de su prioridad y por tanto de su tamaño.
¿Cuando toca ejecutarse un proceso lo hace hasta que el programador de tareas ve que pasó su tiempo y cambia a otro proceso?
Exacto, de esa forma se puede ejecutar de forma concurrente muchos procesos en un solo núcleo.
Un momento, si cada sistema operativo tiene una forma de gestionar los procesos y tiene sus mecánicas para la comunicación entre procesos, ¿eso no hace imposible a un lenguaje mantener el mismo modelo de concurrencia entre diferentes sistemas operativos?
En verdad no, existen las abstracciones. Al final cada sistema operativo tiene sus detalles y nomenclaturas, pero si tomas lo común y ajustas el código según la plataforma a las diferencias, es posible tener un sistema de concurrencia abstracto sobre cualquier sistema operativo.
Totalmente. Como los casos de Erlang con Beam o Java con JVM, estas máquinas virtuales crean un entorno idéntico para sus programas independientemente del sistema operativo donde se ejecutan.
Pero Go no tiene una máquina virtual.
Cierto, compila a nativo. Sin embargo, eso no es una limitación para poder hacer la traducción de esa abstracción antes de conseguir el binario ejecutable.
Entonces, ¿necesitas un binario por cada sistema operativo?
Es lo ideal si quieres optimizar según la plataforma hacia la que compilas.
Resumiendo, Go tiene un sistema abstracto para gestionar los procesos concurrentes desligado de cómo lo hace el sistema operativo donde se ejecuta.
¡Así es! El modelo de concurrencia de Go es MN, es decir, muchas gorrutinas sobre pocos hilos.
¿Todos los sistemas operativos tienen hilos?
Totalmente. El hilo es un elemento esencial para programar la concurrencia dentro de un proceso. Si todo se hiciese por procesos del sistema operativo sería increíblemente ineficiente.
¡Muy cierto! De esa forma, por ejemplo en Linux, un programa de Go puede ejecutarse usando un único proceso y varios hilos dentro de ese proceso, pero con miles de gorrutinas ejecutándose entre esos hilos.
Creo que comienzo a verlo. Si el programador de tareas del sistema operativo Ese donut.
Toroide.
Si en ese donut le damos al proceso de Go una sección, dentro de ese proceso se ejecutan, digamos, 5 hilos que compiten por esa sección, y dentro de cada hilo ponemos 10 gorrutinas. ¿No será esto ineficiente?
¡Para nada! Se te olvida el detalle de que cada hilo se identifica a través del proceso. Pero el programador de tareas no solo reserva espacio para procesos, También para hilos. Además, ese toroide no representa al sistema operativo. Si hay varios procesadores y varios núcleos, existe un toroide por procesador y por núcleo.
Esto se complica innecesariamente.
No te estreses, Chip. Estos detalles normalmente los gestiona el sistema, y ser consciente de ellos nos ayuda a diseñar un mejor programa.
Totalmente. Hay que pensar en la optimización.
Aunque normalmente no es necesario. Lo que sí es necesario es comprender en qué consisten las gorrutinas y cómo se comunican entre ellas usando los canales. Gorrutinas y canales.
¿Por qué le dices gorrutinas y no corrutinas?
Así es como se llaman en Go, pero tienes razón en que se basan en las corrutinas, por lo que supongo se podrían llamar también corrutinas. No obstante, como toda la comunidad las llama gorrutinas, es mejor mantenernos acorde a la comunidad, ¿no?
Las gorrutinas requieren de un procesador de tareas y eso consume tiempo en el procesador.
Pero son óptimas a nivel de uso de memoria, necesitan menos recursos que un proceso o un hilo.
Aunque si creas gorrutinas sin control y no controlas bien la comunicación, pueden crear deadlocks.
Interbloqueos y memory leaks, fugas de memoria.
¿Y qué me dices de la escalabilidad? Gracias al programador de tareas ligero y la posibilidad de crear entre miles o incluso millones de gorrutinas, el sistema escala en vertical muy fácilmente.
¿Escalar en vertical?
Sí, en clústeres se dice que el sistema escala o crece de forma vertical si lo hace dentro de la misma máquina, es decir, usando todos los recursos posibles de la máquina Y si esta máquina dispone de más recursos, aún puede seguir creciendo.
¿Y hay un escalado horizontal?
Sí, es cuando en lugar de dimensionar la máquina colocamos más máquinas donde se ejecute el mismo programa. Si se coordina entre todas las máquinas para llevar a cabo su tarea y agregando más máquinas puede hacer más, entonces se dice que escala horizontalmente.
Lo ideal es reducir la necesidad de recursos. Así escalar es más simple.
Con Go podemos enfocarnos en cualquiera de estas áreas. ¿Escalado vertical? ¡Fácil con las corrutinas y los canales!
¿Y el escalado horizontal?
Bueno, eso no es tan simple.
En Erlang y Elixir se hace más simple.
Y Glimpse, no te olvides de Glimpse.
Pero vayamos con los mensajes, ya que habláis de Erlang, Elixir y Glimpse, que son lenguajes que implementan la comunicación con canales también.
De hecho, la influencia que ejerce Erlang en Go es precisamente en este sentido.
¡Cierto, pro! En Go creamos canales que podemos utilizar para enviar o recibir un tipo de información concreta.
¡Oh, es verdad! Que Go es un lenguaje de tipado estático e inferido. ¿Cómo conseguís que envíe algo por ese canal?
El canal tiene un tipo de datos definido. Por ejemplo, si queremos enviar números reales, elegimos el tipo de dato de coma flotante de 64 bits.
Entonces, ¿puedes enviar números reales a través de este canal?
¡Así es, Chip! Puedo enviar datos con el número pi y recibirlo desde el canal.
Si Go tuviese los mismos tipos que Rust, esto sería mucho más simple para especificar el tipo de mensaje que enviar y su contenido.
Probablemente, pero Go se hizo así por simplicidad. Además, las utilidades de Go permiten comprobar problemas propios de la concurrencia, como las condiciones de carrera.
Pero hablemos de datos más generales. El presente y progresión de Go. Según un artículo de JetBrains titulado en español como ¿Está Go aún creciendo?, revisan diferentes estadísticas hechas a los usuarios de su IDE y otras fuentes de datos para concluir que sí, seguirá su crecimiento.
¡Cierto, pro! Aunque cambió el equipo de dirección, el nuevo equipo respeta el trabajo hecho y seguirá en la misma dirección.
¿Y cuál era esa dirección? ¿Thompson y compañía no son quienes dirigen el futuro del lenguaje?
Me temo que no. Desde 2012 fue Russ Cox quien estuvo al mando y en septiembre de 2024 pasó la antorcha. Después del comunicado se sucedieron muchos agradecimientos hacia Russ Cox por su gran trabajo.
Pero aún hay muchos otros lenguajes siendo usados antes que Go, incluyendo Python, y las estadísticas señalan que el más querido es Rust.
En verdad, Rust y Go no son comparables. Es decir, mientras que Rust se emplea a bajo nivel y para controlar perfectamente el uso de los recursos, como harías con C, Go, al tener una máquina virtual, tiene sus limitaciones a ese nivel.
Por otro lado, Go es más usado cuando el nivel requerido es más alto. Por ejemplo, al desarrollar una utilidad para sistema operativo como Docker, bases de datos como CockroachDB o InfluxDB, sistemas de gestión de colas como NATS, sistemas de estadísticas como Prometheus y un largo etcétera.
Yo sigo viendo mejor Python.
Las empresas que eligen Go lo hacen porque, además de tener una curva de aprendizaje muy baja, no tienen orientación a objetos como Python u otros lenguajes. No tiene muchas estructuras de control como Rust y su código es muy similar a Pascal.
No creo que sea más fácil de aprender que Python, pero es verdad que quizá dominar Python cuesta un poco más.
Además, desplegar una aplicación es copiar un fichero binario.
Limpio y sencillo, como con Rust.
Es verdad que en Python hay que instalar los requisitos antes y la versión de Python. Pero tampoco es tan difícil, lo tenemos muy automatizado ya.
No es difícil, Chip, pero hay que hacerlo. Una de las ventajas de la compilación a nativo es que se copia el binario y se ejecuta.
Pero no puedes cambiar el código de forma rápida en producción.
Fingiré que no has sugerido cambiar código en producción a mano. Conclusiones.
Es siempre un placer hacer un capítulo hablando de temas tan interesantes.
Has estado en tu salsa.
En este capítulo hemos visto un poco sobre la concurrencia en Go, las gorrutinas, los canales y algunas comparaciones con Python.
Eso no ha sido justo. Python sobresale en muchos aspectos y puedo demostrarlo.
Ofrece el tema en la siguiente votación y lo vemos.
Pues vamos con los comentarios, que tengo la pregunta perfecta para que pongáis en la caja de comentarios: ¿Sabíais que Nim es una versión nativa y de alto rendimiento con sabor a Python?
¡Chip! Esa pregunta no tiene nada que ver con lo que hemos hablado hoy. Las preguntas importantes son: ¿Habéis utilizado las gorrutinas y los canales en vuestros desarrollos con Go?
¿Habéis tenido que depurar y optimizar código hecho en Go?
Déjanos tu comentario.
He visto por ahí a Byte y Slice, pero no han dicho nada durante todo el vídeo. ¿No les reservaste nada que decir?
Después de los proverbios que había que decir en el vídeo anterior, no se me ocurría nada relacionado con la concurrencia para que ellos dijesen.
¿Tampoco chistes?
De esos tienen un montón. Mira, por ahí están. ¿Sabes qué hace un gofer cuando se cansa? No, ¿el qué?
Runtime goes sleep.
Eso no existe.
Por eso nunca descansamos. ¿Sabes por qué no puedes confiar en una variable global?
No, ¿por qué?
Porque siempre está metida en todo. ¿Sabes qué hace un gofer cuando se enamora? No, ¿el qué? Se bloquea esperando una señal.
¿Y esto tiene gracia?
Mucha, pero es humor inteligente.
Lo que hay que oír. Bueno, voy a hablar con Byte y Slice a ver si me ayudan con algo.
Continuará.
More from Manuel

Historia de los Lenguajes de Programación: años 1960-1969 #basic #apl #simula #lisp #logo

¿Compilador o intérprete? La historia de una rivalidad que comenzó en los años 40








