Thread

! В Ruby используется Global Locking вместо реальных потоков, так что особо бояться состояния гонки не стоит !

В век компьютеров с несколькими ядрами на одном кристале глупо забывать о таком прекрасно способе оптимизации, как многопоточность. Суть данной возможности проста - при наличии больше, чем одного ядра можно разделить сложную задачу на несколько маленьких, которые с помощью шедулера ОС будут аккуратно намазаны на несколько ядер, нежели один поток, который будет постоянно крутиться на одном ядре.

Создание потока

Здесь всё просто. Даже не надо подключать никакие модули. Берём класс Thread, даём ему блок и voila - мы создали поток.

threads = []
threads << Thread.new do
  puts 'Thread 1 started'
  sleep 2
  puts 'Thread 1 finished'
end
 
threads << Thread.new do
  puts 'Thread 2 started'
  sleep 1
  puts 'Thread 2 finished'
end
 
while threads.length > 0
  threads.delete_if do |t|
    unless t.alive?
      t.join
      true
    end
  end
end

Если не добавить в конце цикл, отслеживающий состояние потоков и правильно их не завершающий - родительский поток закончит своё выполнение и завершится, прервав все дочерние потоки.

Мьютексы

Каждый, кто хоть раз занимался многопоточным программированием, сталкивался с состоянием гонки - процессом, когда два или более потока в рандомное время обращаются к одному и тому же ресурсу. Это может привести к тому, что два потока обратятся к одному счётчику одновременно и вместо того, чтобы он увеличился на 2, он увеличится на 1, поскольку каждый поток запишет своё значение. Для того, чтобы состояние гонки не возникало - при работе с общими ресурсами применяют мьютексы (блокировки). В ruby они сделаны максимально прозрачно (не надо думать о том, залочил ты мьютекс, разлочил, всё ли почистил)

class Sheep
  def initialize
    @shorn = false
  end
 
  def shorn?
    @shorn
  end
 
  def shear!
      print "shearing... "
      @shorn = true
  end
end
 
 
sheep = Sheep.new
 
5.times.map do
  Thread.new do
    unless sheep.shorn?
      sheep.shear!
    end
  end
end.each(&:join)

Если позапускать данный код несколько раз - можно увидеть, что овечку иногда бреют аж до пяти раз, хотя по идее после первого раза переменная должна быть установлена в нужные значения. После добавления мьютекса всё становится хорошо:

class Sheep
  def initialize
    @shorn = false
    @mutex = Mutex.new
  end
 
  def shorn?
    @shorn
  end
 
  def shear!
      print "shearing... "
      @shorn = true
  end
 
  def mutex
    @mutex
  end
end
 
 
sheep = Sheep.new
 
5.times.map do
  Thread.new do
    sheep.mutex.synchronize do
      unless sheep.shorn?
        sheep.shear!
      end
    end
  end
end.each(&:join)
 
puts ""