Стендап Сьогодні 📢 Канал в Telegram @stendap_sogodni

🤖🚫 AI-free content. This post is 100% written by a human, as is everything on my blog. Enjoy!

30.01.2024

Паралельні обчислення в Ruby

Я вже зачіпляв паралелізацію запитів в Ruby, але тут стало питання про доповнення Ruby бінарним кодом, та я вирішив нагадати собі, як там паралелізація працює.

Взагалі, в одному процесі Ruby може одночасно працювати тільки один потік коду. Це те, що називається GVL - Global VM Lock, раніше відомий як GIL. Віртуальна машина Ruby не розрахована на паралельне виконання.

Міф: раніше в Ruby були “зелені” потоки, а тепер системні, тож сучасний Ruby здатний на паралелізацію. Так, клас Thread створює системні потоки — які можуть бути запущені паралельно на всіх ядрах процесора. Все одно — через GVL всі окрім одного потоку будуть заблокованими.

Правда: щоб досягти справжнього паралельного виконання, потрібно створити декілька процесів (наприклад, через Process.fork). Це працює тому, що кожний процес — це окрема копія віртуальної машини, з власним GVL. Але, звісно, ми втрачаємо спільний простір змінних; окрім того, створення процесів повільне та не підходить для “локальної” паралелізації.

Всі серверні програми на Ruby використовують як процеси — щоб залучити всі ядра процесора, так і потоки — щоб ефективно обробляти ввід/вивід.

Сучасний нюанс: тепер є ще клас Ractor. У кожного Ractor свій GVL. Магії немає — рактори, як і процеси, ізолюють свої змінні. Зате рактори створюються швидше процесів (кожний рактор — окремий потік) та мають зручний механізм обміну даними. Рактори дозволяють виконувати на Ruby справжні паралельні обчислення.

Та, останній момент, про бінарні бібліотеки: звичайний виклик функції з бібліотеки нічого з GVL не робить та на логіку не впливає. Проте тут є спосіб відімкнути GVL, викликом rb_thread_call_without_gvl. Це відкриває шлях для паралельного виконання іншого потоку, якщо такий є. Звісно, без GVL не можна робити нічого з Ruby, зате можна робити внутрішні обчислення чи ввід/вивід.