Common Multithread Patterns
В многопоточном программировании для эффективного управления потоками и обеспечения их безопасного и результативного выполнения используются определённые шаблоны параллельной обработки.
Шаблон "Производитель-потребитель"
«Производитель-потребитель» - это классический шаблон параллельной обработки, в котором один или несколько потоков (производителей) генерируют данные, а один или несколько потоков (потребителей) обрабатывают эти данные. Этот шаблон помогает сбалансировать нагрузку между задачами производства и потребления.
Класс Queue в Ruby из библиотеки thread часто используется для реализации шаблона «производитель-потребитель».
# Создание FIFO (First-In-First-Out) очереди
queue = Queue.new
# Производственный поток
producer = Thread.new do
10.times do |i|
puts "Producing item #{i}"
queue << i
sleep(rand(0.1..0.5)) # Имитация переменного времени производства
end
queue << nil # Сигнал указывающее на окончание производства
end
# Потребительский поток
consumer = Thread.new do
loop do
item = queue.pop
break if item.nil? # Выход при обнаружении контрольного значения
puts "Consuming item #{item}"
sleep(rand(0.2..0.6)) # Имитация переменного времени потребления
end
end
# Ожидание завершения выполнения обоих потоков
[producer, consumer].each(&:join)
# => Producing item 0
# => Consuming item 0
# => Producing item 1
# => Consuming item 1
# => Producing item 2
# => Producing item 3
# => Consuming item 2
# => Producing item 4
# ...
# => Consuming item 8
# => Consuming item 9
Шаблон рабочего пула
Шаблон «Рабочий пул» предполагает создание пула рабочих потоков, которые обрабатывают задачи из общей очереди. Этот шаблон полезен для распараллеливания задач и повышения пропускной способности.
# Создание FIFO (First-In-First-Out) очереди
queue = Queue.new
num_workers = 3
# Заполнение очереди задачами
5.times do |i|
queue << i
end
# Создание рабочих потоков
workers = num_workers.times.map do |worker_id|
Thread.new do
while (task = queue.pop(true) rescue nil)
puts "Worker #{worker_id} processing task #{task}"
sleep(rand(0.1..0.5)) # Имитация времени обработки
end
end
end
# Ожидание завершения выполнения всеми рабочими потоками
workers.each(&:join)
# => Worker 0 processing task 0
# => Worker 1 processing task 1
# => Worker 2 processing task 2
# => Worker 0 processing task 3
# => Worker 0 processing task 4
Потокобезопасный шаблон Singleton
Этот шаблон обеспечивает потокобезопасность класса-одиночки. В Ruby для реализации этого шаблона можно использовать Mutex
require 'singleton'
class Logger
# Обеспечение создания только одного экземпляра класса
include Singleton
def initialize
@log = []
@mutex = Mutex.new
end
def log(message)
# блок кода может выполняться только одним потоком
# одновременно, что делает метод потокобезопасным
@mutex.synchronize do
@log << message
puts "Logged: #{message}"
end
end
def show_log
# Кумулятивный эффект одновременного логирования
@log.each { |message| puts message }
end
end
threads = 5.times.map do |i|
Thread.new do
Logger.instance.log("Message from thread #{i}")
end
end
# Ожидание завершения выполнения всеми рабочими потоками
threads.each(&:join)
Logger.instance.show_log
# => Logged: Message from thread 0
# => Logged: Message from thread 1
# => Logged: Message from thread 2
# => Message from thread 0
# => Message from thread 1
# => Message from thread 2