Ruby 3+ представил революционный механизм Ractor, который наконец-то позволяет достичь настоящей параллельности в MRI Ruby, обходя ограничения GIL. В статье разберём, как работают Ractor’ы в Ruby 3.4, какие объекты можно передавать между ними и как избежать типичных ошибок при работе с этим мощным инструментом для CPU-bound задач.
В MRI Ruby потоки не дают настоящей параллельности из-за GIL.
Но Ruby 3+ принёс новую надежду — Ractor. А в 3.4 стало ещё лучше.
🧬 Что такое Ractor?
Ractor (Ruby Actor) — механизм, позволяющий запускать несколько Ruby-интерпретаторов параллельно.
- Каждому
Ractor— своя память - Общение только через сообщения
- Нет GIL между Ractor’ами
🚀 Пример: параллельное вычисление
r1 = Ractor.new { Ractor.yield 1 + 2 }
r2 = Ractor.new { Ractor.yield 3 + 4 }
puts r1.take # => 3
puts r2.take # => 7
Это выполняется параллельно.
⚠️ Но есть нюанс: объект должен быть “shareable”
r = Ractor.new do
puts "Я получил: #{Ractor.receive}"
end
r.send("hello") # 💥 Ractor::Error: can’t send non-shareable object
🧊 Что можно передавать?
- Immutable объекты:
true,false,nil, числа, символы, frozen-строки Object.new.freeze— работает- Массив из
shareable-элементов — тоже работает
Ractor.new do
puts Ractor.receive.inspect
end.send([1, 2, 3].freeze) # => [1, 2, 3]
🆕 Ruby 3.4 — что нового для Ractor?
-
📦
make_shareable(obj)— безопасно делает объект пригодным для передачи:obj = {a: 1} Ractor.make_shareable(obj) - ✅ Улучшена поддержка
Symbol,Struct,Hash - 🔥 Новое поведение ошибок внутри Ractor — можно ловить
Ractor::RemoteError - 📈 Более стабильная производительность при
spawnиjoin
🧪 Коммуникация между Ractor’ами
r = Ractor.new do
msg = Ractor.receive
Ractor.yield "Привет, #{msg}"
end
r.send("Ruby")
puts r.take # => Привет, Ruby
Важно: передача односторонняя.
Для двусторонней — передавай Ractor.current:
r = Ractor.new do
from = Ractor.receive
from.send("👋 Привет из Ractor")
end
r.send(Ractor.current)
puts Ractor.receive
🧠 Где это применимо?
- Обработка CPU-bound задач (шифрование, компрессия)
- Параллельная сериализация/десериализация
- Safe background processing без
fork - Безопасный runtime для
eval, sandbox
🔥 Частые ошибки
r = Ractor.new { puts "ok" }
puts r.class # => Ractor
r.send(Object.new) # 💥 Ractor::Error: can’t send non-shareable object
📌 Используй .freeze, Ractor.make_shareable, или работай через JSON.
🔚 Вывод: Ractor — первая настоящая многопоточность в MRI Ruby. В 3.4 — уже пригоден к жизни. Да, есть ограничения, но если тебе нужно реально параллельно — теперь есть способ.