El Global VM Lock (GVL, antes llamado GIL en CRuby) es el mecanismo que impide que varios threads de Ruby ejecuten código Ruby al mismo tiempo dentro del mismo proceso. Es la razón por la que los threads de Ruby no aprovechan múltiples núcleos de CPU para código Ruby puro. Para operaciones de entrada/salida (red, disco) sí funciona bien porque el GVL se libera mientras se espera. Pero para cómputo intensivo en CPU, los threads de Ruby no escalan.
Ruby 3.0, lanzado en diciembre de 2020, introdujo Ractors como solución experimental para este problema. La idea es ofrecer concurrencia paralela real sin eliminar el GVL del intérprete principal.
Cómo funciona un Ractor
Un Ractor es una unidad de ejecución con su propio espacio de memoria. No comparte objetos con otros Ractors directamente, y eso es exactamente lo que permite ejecutarlos en paralelo: al no compartir memoria mutable, no necesitan el GVL para coordinarse.
# Ractor básico
r = Ractor.new do
puts "Ejecutando en el Ractor"
"resultado"
end
resultado = r.take # Espera a que el Ractor termine y recoge el valor
puts resultado # => "resultado"
# Múltiples Ractors en paralelo
ractors = 4.times.map do |i|
Ractor.new(i) do |id|
# Cálculo intensivo en CPU
suma = (1..1_000_000).sum
"Ractor #{id}: #{suma}"
end
end
resultados = ractors.map(&:take)
resultados.each { |r| puts r }
Los Ractors se ejecutan en hilos del sistema operativo, pero sin el GVL entre ellos. En un sistema con 4 núcleos, 4 Ractors pueden ejecutarse literalmente en paralelo.
Comunicación entre Ractors
Al no compartir memoria, los Ractors se comunican pasando mensajes. Hay dos mecanismos: push (enviar al inbox de otro Ractor) y pull (recibir del inbox propio).
# Comunicación con send y receive
productor = Ractor.new do
5.times do |i|
Ractor.yield i * 2 # Envía un valor al que esté esperando
end
end
# El Ractor principal recibe los valores
5.times do
valor = productor.take
puts "Recibido: #{valor}"
end
# Recibido: 0
# Recibido: 2
# Recibido: 4
# ...
# Canal compartido entre múltiples Ractors
canal = Ractor.new do
loop do
tarea = Ractor.receive # Espera un mensaje
resultado = tarea ** 2
Ractor.yield resultado
end
end
canal.send(5)
puts canal.take # => 25
La restricción de objetos compartibles
La restricción más importante de los Ractors es que no puedes pasar objetos mutables entre ellos directamente. Solo se pueden compartir objetos inmutables (frozen) o se hace una copia profunda.
# Esto funciona: strings frozen
texto = "hola".freeze
r = Ractor.new(texto) { |t| t.upcase }
puts r.take # => "HOLA"
# Esto falla: string mutable
texto = "hola"
r = Ractor.new(texto) { |t| t.upcase }
# Ractor::IsolationError: can not send a shareable object
# Solución: pasar una copia (move: true transfiere la propiedad)
r = Ractor.new(texto, move: true) { |t| t.upcase }
# texto ya no es accesible desde el Ractor principal
puts r.take # => "HOLA"
Esta restricción hace que muchas gemas existentes no sean compatibles con Ractors directamente. Las gemas que tienen estado global mutable (la mayoría de las gemas antiguas) no se pueden usar dentro de Ractors sin adaptación.
Estado actual y limitaciones
A finales de 2025, los Ractors siguen siendo experimentales en Ruby. Cada vez que los usas, Ruby muestra un warning. La API puede cambiar entre versiones.
Las limitaciones principales son:
- Compatibilidad con gemas: la mayoría de las gemas no son «Ractor-safe» todavía. Usar una gema incompatible dentro de un Ractor produce errores en tiempo de ejecución.
- Rails no es compatible: Rails no funciona dentro de Ractors. El framework tiene demasiado estado global mutable.
- Debugging complejo: los errores entre Ractors son más difíciles de rastrear que los errores en código secuencial.
Cuándo usar Ractors hoy
Los Ractors son útiles en escenarios concretos donde el paralelismo real en CPU importa y no dependes de gemas incompatibles. Los mejores casos de uso actuales son:
- Procesado paralelo de datos: parsear CSV grandes, transformar imágenes, calcular estadísticas.
- Servidores de alto rendimiento escritos desde cero con Ractors (Falcon HTTP server ya experimenta con esto).
- Cálculos científicos o criptográficos donde el cuello de botella es la CPU.
Para la mayoría de las aplicaciones Rails o Sinatra, los Ractors no aportan nada todavía. En esos contextos, el paralelismo sigue logrando mejor resultado con múltiples procesos (Puma multi-worker, por ejemplo) que con Ractors dentro de un proceso.
El camino hacia Ruby sin GVL
Los Ractors son un paso en la dirección de un Ruby capaz de aprovechar múltiples núcleos. El equipo de Ruby también trabaja en YJIT con soporte multihilo (disponible en Ruby 3.4) y en reducir el impacto del GVL en casos de uso comunes.
La visión a largo plazo apunta a Ruby 4 con un GVL opcional o eliminado, pero eso requiere que todo el ecosistema de gemas se adapte, lo que llevará años. Los Ractors son el primer paso concreto en esa dirección.
Si te interesa la concurrencia desde otro ángulo, el artículo sobre FastAPI en 2026 explica cómo Python resuelve el problema similar con async/await y el event loop de uvicorn.
Imagen: Pexels / Nicolas Foster
