Ruby on Rails за замовчуванням працює в однопоточному режимі, але він може бути багатопотоковим в залежності від веб-сервера та конфігурації.
🔹 1️⃣ Що таке однопоточність в Rails?
📌 Визначення:
- Однопоточний додаток Rails обробляє тільки один запит за раз для кожного робітника.
- Якщо один запит повільний, інші повинні чекати, поки він не завершиться.
📌 Як це працює:
- За замовчуванням Ruby MRI (Matz’s Ruby Interpreter) має глобальний блокувальник інтерпретатора (GIL), що означає, що лише один потік виконує Ruby код одночасно.
- Навіть якщо ви увімкнете кілька потоків, GIL Ruby не дозволяє справжнє паралельне виконання Ruby коду.
📌 Приклад однопотокового виконання:
Уявіть, що два користувачі запитують сторінку. В однопоточному Rails другий запит повинен чекати, поки перший не завершиться.
Запит 1 ───► Обробка ───► Відповідь (3с)
Запит 2 ───► ЧЕКАННЯ ───► Обробка ───► Відповідь (3с)
⏳ Загальний час: 6с для обох запитів
📌 Коли використовується однопоточність?
- Якщо Rails працює з WEBrick (стандартний сервер Rails).
- Якщо використовується тільки масштабування на основі процесів (наприклад, Unicorn без потоків).
🔹 2️⃣ Що таке багатопоточність в Rails?
📌 Визначення:
- Багатопоточний Rails може обробляти багато запитів одночасно в одному процесі.
- Кожен запит виконується в окремому потоці, що підвищує паралельність.
📌 Як це працює:
- Puma (стандартний сервер в Rails) дозволяє використовувати кілька потоків для кожного робітника.
- Якщо один запит чекає (наприклад, на базу даних), інший запит може продовжити обробку.
📌 Приклад багатопотокового виконання (2 потоки):
Запит 1 ───► Потік 1 ───► Відповідь (3с)
Запит 2 ───► Потік 2 ───► Відповідь (3с)
✅ Загальний час: 3с для обох запитів (замість 6с)
📌 Коли використовується багатопоточність?
- Якщо Rails працює з Puma (
threads 5,5
) або Passenger Enterprise. - Якщо використовується пулінг з’єднань ActiveRecord (
pool: 10
вdatabase.yml
). - Якщо додаток в основному I/O-зав’язаний (чекає на базу даних, API тощо), багатопоточність допомагає.
🔹 3️⃣ Чи слід використовувати багатопоточність в Rails?
✅ Використовуйте багатопоточність, якщо:
- Ваш додаток обробляє багато одночасних запитів (як API).
- Ви використовуєте Puma або Passenger та увімкнули потоки (
threads 5,5
). - Ви хочете покращити час відповіді та використання CPU.
❌ Уникайте багатопоточності, якщо:
- Ви використовуєте MRI Ruby і виконуєте багато важких для CPU завдань (GIL обмежує переваги).
- Ваш додаток модифікує спільну пам'ять (може призвести до умов гонки).
- Ви не налаштували пулінг з’єднань з БД (може призвести до блокувань).
🔹 4️⃣ Як увімкнути багатопоточність в Rails?
✅ Використовуйте Puma та налаштуйте потоки (config/puma.rb
):
# Дозволяє Puma обробляти кілька потоків
threads 5, 5
workers 2 # Включає також багатопроцесорність
preload_app!
✅ Оптимізуйте пулінг з’єднань з БД (config/database.yml
):
production:
adapter: postgresql
pool: 10 # Повинен відповідати або перевищувати кількість потоків
✅ Перенесіть важкі завдання в фонові роботи:
MyJob.perform_later(user) # Використовуйте Sidekiq для кращої безпеки потоків
🏆 Остаточний висновок: Чи має Rails бути однопоточним чи багатопоточним?
🚀 Багатопоточність (з Puma) рекомендується для більшості додатків Rails, якщо тільки у вас немає завдань, що займають багато CPU.
🔹 Як працюють процесори та потоки в багатопоточності
Процесорний ядро — це фізичний обчислювальний блок, а потік — це віртуальна одиниця виконання, яку керує процесор. Сучасні процесори підтримують багатопоточність (Hyper-Threading в Intel), де одне ядро може обробляти кілька потоків.
1️⃣ Як процесор розподіляє роботу між потоками
🔹 Якщо у вас 4-ядерний процесор і кожне ядро підтримує 2 потоки, ваша система може обробляти максимум 8 одночасних потоків.
🔹 Потоки керуються планувальником процесора, який надає час виконання кожному потоку.
🔹 Якщо ваш додаток створює більше потоків, ніж доступні потоки процесора, система використовує часове розподілення (time slicing), де кожен потік отримує невеликий часовий слот для виконання, перш ніж перейти до наступного.
2️⃣ Що відбувається, якщо потоків більше, ніж ядер процесора?
Приклад: 4-ядерний процесор з 8 доступними потоками
Сценарій 1: Додаток використовує лише 4 потоки
✅ Кожен потік працює на окремому ядрі → немає перемикання контексту → найкраща продуктивність
Сценарій 2: Додаток використовує 8 потоків (відповідає ліміту процесора)
✅ Кожен потік отримує свій потік процесора → хороша продуктивність, але можливе змагання за ресурси
Сценарій 3: Додаток використовує 16+ потоків (більше за ліміт процесора)
⏳ ОС повинна постійно планувати і перемикати потоки → накладні витрати збільшуються → повільніша продуктивність
3️⃣ Як працює планування потоків, коли потоків більше, ніж ядер процесора?
Коли в додатку більше потоків, ніж доступних потоків процесора, планувальник ОС виконує наступні кроки:
1️⃣ Кожному ядру процесора призначається певна кількість потоків
2️⃣ ОС виконує часовий розподіл (наприклад, потік працює кілька мілісекунд, потім наступний потік переходить до роботи)
3️⃣ Перемикання контексту відбувається, коли процесор зберігає стан одного потоку і завантажує інший
4️⃣ Більше потоків = більше накладних витрат на перемикання, що уповільнює виконання
🔹 Візуальне уявлення (4-ядерний процесор, 8 потоків, 16 потоків додатку)
Фізичні ядра процесора: 1 2 3 4
Логічні потоки: 1 2 3 4 5 6 7 8
Потоки додатку: A B C D E F G H I J K L M N O P
Часовий слот 1: A B C D (E, F, G, H чекають)
Часовий слот 2: E F G H (A, B, C, D чекають)
Часовий слот 3: I J K L (M, N, O, P чекають)
Часовий слот 4: M N O P (I, J, K, L чекають)
🚀 Висновок: Чим більше потоків ви маєте за межами ліміту процесора, тим більше часу витрачається на перемикання контексту, що знижує ефективність.
4️⃣ Як оптимізувати багатопоточність в Rails, враховуючи кількість ядер процесора?
✅ Якщо процесор має 4 ядра та 8 потоків, налаштуйте Puma таким чином (config/puma.rb
):
workers 2 # два процеси (кожен процес використовує кілька потоків)
threads 4, 4 # кожен процес має 4 потоки (всього: 2×4 = 8 потоків)
- Робітники використовують окремі ядра процесора (добре для багатоядерних систем).
- Потоки покращують паралельність без надмірних витрат на перемикання контексту.
🏆 Остаточний висновок: Скільки потоків слід використовувати?
🚀 Золоте правило: Залишайте потоки на робітника ≤ потоки на ядро процесора, щоб уникнути надмірного перемикання контексту!
LinkedIn :- https://www.linkedin.com/in/vaibhav-jain-6454971a4
Перекладено з: Single-Threaded vs Multi-Threaded in Ruby on Rails 🔥🔥