Потоки (Threads) в Ruby — это легковесные единицы выполнения, которые позволяют организовать конкурентное выполнение кода. Разберём их работу и оптимальные сценарии использования.
# Простой пример создания потоков
threads = []
3.times do |i|
threads << Thread.new do
puts "Поток #{i} начал работу"
sleep rand(1..3)
puts "Поток #{i} завершил работу"
end
end
threads.each(&:join)
I/O-bound операции:
Параллельные независимые задачи:
Фоновые задачи:
# Пример: параллельные HTTP-запросы
require 'net/http'
urls = ['https://api.example.com/users', 'https://api.example.com/products']
threads = urls.map do |url|
Thread.new do
response = Net::HTTP.get(URI(url))
puts "Ответ от #{url}: #{response.size} байт"
end
end
threads.each(&:join)
CPU-bound задачи:
Критические секции:
Длительные блокирующие операции:
# Пример с Mutex
@counter = 0
@mutex = Mutex.new
10.times.map do
Thread.new do
@mutex.synchronize do
temp = @counter
sleep 0.001
@counter = temp + 1
end
end
end.each(&:join)
puts @counter # Всегда 10
Сценарий | Альтернатива | Преимущества |
---|---|---|
CPU-bound задачи | Процессы (fork) | Настоящий параллелизм |
Высоконагруженные IO | EventMachine | Лучшая масштабируемость |
Распределённые задачи | Sidekiq/Resque | Устойчивость, очереди |
Ограничивайте число потоков:
# Используйте пул потоков
require 'concurrent'
pool = Concurrent::FixedThreadPool.new(5)
Обрабатывайте исключения:
Thread.new do
begin
risky_operation
rescue => e
puts "Ошибка в потоке: #{e.message}"
end
end
Избегайте разделяемого состояния:
# Лучше
results = []
threads = 3.times.map { |i| Thread.new { results[i] = process(i) } }
Используйте Thread.current для хранения состояния:
Thread.current[:request_id] = SecureRandom.uuid
Резюмируем: Потоки в Ruby — мощный инструмент для I/O-bound задач, но требуют аккуратного обращения. Используйте их для операций с ожиданием, избегайте для CPU-bound задач, всегда синхронизируйте доступ к общим ресурсам. Для сложных сценариев рассмотрите пулы потоков или альтернативные подходы к параллелизму.