Конкурентность и параллелизм — это две важные концепции многозадачности, которые часто путают. Рассмотрим их различия на примерах из Ruby.
Основные определения
Конкурентность
- Что это: Возможность выполнять несколько задач видимо одновременно
- Как работает: Быстрое переключение между задачами
- Аналог из жизни: Шеф-повар, который готовит несколько блюд, переключаясь между ними
Параллелизм
- Что это: Реальное одновременное выполнение задач
- Как работает: Использование нескольких процессоров/ядер
- Аналог из жизни: Команда поваров, где каждый готовит свое блюдо
Примеры в Ruby
1. Конкурентность с потоками
threads = []
3.times do |i|
threads << Thread.new do
# Имитация IO-операции
sleep(1)
puts "Thread #{i} done"
end
end
threads.each(&:join)
# Выполнится за ```1 секунду (GIL ограничивает реальный параллелизм)
2. Параллелизм с процессами
pids = []
3.times do |i|
pids << fork do
# CPU-интенсивная задача
5.times { |j| j**j }
puts "Process #{i} done"
end
end
Process.waitall
# На многоядерном CPU выполнится быстрее, чем последовательно
Ключевые различия
Характеристика | Конкурентность | Параллелизм |
---|
Реальное одновременное выполнение | Нет | Да |
Использует несколько ядер CPU | Нет (в MRI) | Да |
Подходит для IO-bound задач | Да | Не обязательно |
Подходит для CPU-bound задач | Нет | Да |
Общая память | Да (потоки) | Нет (процессы) |
Сложность отладки | Средняя | Высокая |
GIL в Ruby
- Что делает: Ограничивает выполнение Ruby-кода одним потоком за раз
- Влияние:
- MRI Ruby: только конкурентность (не истинный параллелизм потоков)
- JRuby/TruffleRuby: настоящий параллелизм потоков
# В MRI этот код не получит выигрыша от многопоточности
threads = Array.new(4) { Thread.new { 5.times { |i| i**i } } }
threads.each(&:join)
Практические рекомендации
Когда использовать конкурентность:
- Приложения с большим количеством IO (веб-серверы)
- Когда нужно поддерживать отзывчивость UI
- Для фоновых задач, не требующих CPU
Когда использовать параллелизм:
- CPU-интенсивные вычисления
- Обработка больших данных
- Когда доступно несколько ядер CPU
Комбинированный подход
Часто используют оба подхода вместе:
# Процессы для параллелизма + потоки внутри для конкурентности
2.times do
fork do
3.times.map do |i|
Thread.new { network_request(i) } # IO внутри процесса
end.each(&:join)
end
end
Process.waitall
Резюмируем
- Конкурентность — это про эффективное использование одного ядра за счет переключения между задачами
- Параллелизм — это про использование нескольких ядер для реального одновременного выполнения
В Ruby:
- Потоки (Threads) дают конкурентность (в MRI)
- Процессы (fork) дают параллелизм
- Выбор зависит от типа задач (IO-bound vs CPU-bound)
- Для максимальной производительности часто комбинируют оба подхода
Помните:
- Для веб-приложений обычно важнее конкурентность
- Для вычислений — параллелизм
- GIL в MRI Ruby ограничивает параллелизм потоков
- Альтернативные реализации Ruby (JRuby) позволяют настоящий параллелизм потоков