Вытеснение и параллелизм — разные свойства. В CRuby/MRI потоки могут переключаться без явного yield в пользовательском коде, но GVL ограничивает одновременное выполнение Ruby-кода потоками одного Ractor. Во время многих блокирующих операций I/O GVL освобождается. JRuby не имеет такого глобального ограничения для Ruby-потоков. Fibers используют кооперативное переключение; scheduler не превращает их в CPU-параллельные потоки.
Как в Ruby реализована вытесняющая многозадачность?
Почему переключение потоков не равно параллельному исполнению, как влияет GVL в CRuby и чем отличаются JRuby и fibers.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Сначала разделим понятия. Конкурентность позволяет нескольким задачам продвигаться, параллелизм — исполняться одновременно на разных вычислительных ресурсах, а вытеснение означает переключение без обязательного добровольного yield самой задачи.
В CRuby/MRI Thread не требует, чтобы каждая Ruby-программа вручную уступала управление. Планирование использует механизмы runtime и операционной системы. При этом GVL допускает выполнение Ruby-кода только одним потоком внутри одного Ractor в конкретный момент. Поэтому два CPU-нагруженных Ruby-потока в обычном одном Ractor не получают автоматически ускорение на двух ядрах.
Во время многих блокирующих операций ввода-вывода GVL освобождается, и другой поток может работать. Нативное расширение также может специально выполнять безопасную часть работы без GVL. Но утверждать, что любой C-код или любой вызов освобождает блокировку, нельзя. GVL не заменяет синхронизацию бизнес-операций над общим состоянием. Thread и блокирующие операции.
Эти ограничения нельзя приписывать всем реализациям Ruby. Например, JRuby использует JVM-потоки и поддерживает их параллельное выполнение; корректность общего изменяемого состояния всё равно требует синхронизации. Конкурентность в JRuby.
Для CPU-параллелизма в CRuby рассматривают процессы, подходящие нативные библиотеки или Ractor с его ограничениями обмена объектами. Fiber Scheduler решает другую задачу: организует кооперативную работу fibers, особенно при ожидании I/O. Он не делает произвольное CPU-вычисление в одном потоке параллельным. Ractor, Fiber.
На собеседовании важно назвать реализацию Ruby и характер нагрузки; универсальный ответ «в Ruby нет вытеснения из-за GIL» неверен.