Объясните, как работает конкурентность.ruby-57

Конкурентность — это способ выполнения нескольких задач в перекрывающиеся периоды времени. В Ruby она реализуется особенным образом из-за GIL (Global Interpreter Lock).

1. Основные концепции

Параллелизм vs Конкурентность

  • Параллелизм - одновременное выполнение (требует нескольких ядер CPU)
  • Конкурентность - видимость одновременного выполнения (может работать на одном ядре)

2. Механизмы конкурентности в Ruby

Потоки

threads = []
3.times do |i|
  threads << Thread.new do
    sleep rand(0.1..0.3)
    puts "Поток #{i} выполнился"
  end
end
threads.each(&:join)

Fibers

fiber = Fiber.new do
  puts "Первая часть"
  Fiber.yield
  puts "Вторая часть"
end

fiber.resume
fiber.resume

Ractors

# Создаем рактор
ractor = Ractor.new do
  Ractor.yield "Результат работы"
end

# Получаем результат
puts ractor.take

3. GIL

  • Один "глобальный замок" на интерпретатор Ruby
  • Позволяет выполняться только одному потоку Ruby за раз
  • Исключение: I/O операции освобождают GIL

4. Модель событий

Пример с Async:

require 'async'

Async do |task|
  3.times do |i|
    task.async do
      sleep rand(0.1..0.3)
      puts "Задача #{i} выполнена"
    end
  end
end

5. Практические паттерны

Пулы потоков

require 'concurrent'

pool = Concurrent::FixedThreadPool.new(5)
10.times do |i|
  pool.post do
    puts "Обработка задачи #{i} в потоке #{Thread.current.object_id}"
  end
end
pool.shutdown
pool.wait_for_termination

Атомарные операции

@counter = Concurrent::AtomicFixnum.new(0)

10.times.map do
  Thread.new do
    100.times { @counter.increment }
  end
end.each(&:join)

puts @counter.value # => 1000

6. Проблемы и решения

Состояние гонки

# Проблема
@value = 0

10.times.map do
  Thread.new do
    100.times { @value += 1 }
  end
end.each(&:join)

puts @value # Может быть меньше 1000

Взаимная блокировка

mutex1 = Mutex.new
mutex2 = Mutex.new

Thread.new do
  mutex1.synchronize do
    sleep 1
    mutex2.synchronize { puts "Поток 1" }
  end
end

Thread.new do
  mutex2.synchronize do
    sleep 1
    mutex1.synchronize { puts "Поток 2" }
  end
end

7. Выбор подхода

МеханизмЛучше дляОсобенности
ThreadsI/O-bound задачиGIL ограничивает CPU-операции
FibersЛегковесные задачиКооперативная многозадачность
RactorsCPU-bound задачиЭкспериментальный, Ruby 3+
Event LoopСетевые приложенияАсинхронный стиль программирования

Резюмируем: Конкурентность в Ruby строится вокруг потоков с GIL, что делает её особенно эффективной для I/O-bound задач. Для CPU-bound операций рассматривайте Ractors или процессы. Используйте потокобезопасные структуры данных из concurrent-ruby для сложных сценариев. Понимание этих механизмов критически важно для создания масштабируемых Ruby-приложений.