Книга: Программирование на языке Ruby

13.2.4. Условные переменные

13.2.4. Условные переменные

Да зовите моих скрипачей, трубачей...

«Веселый король» (детский стишок)[16]

Условная переменная — это, по существу, очередь потоков. Они используются в сочетании с мьютексами для лучшего управления синхронизацией потоков.

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

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

Рассмотрим несколько искусственный пример в духе задачи об обедающих философах. Представьте себе, что вокруг стола сидят три скрипача, ожидающих своей очереди поиграть. Но у них есть всего две скрипки и один смычок. Понятно, что скрипач сможет играть, только если одновременно завладеет одной из скрипок и смычком.

Мы поддерживаем счетчики свободных скрипок и смычков. Когда скрипач хочет получить скрипку и смычок, он должен ждать их освобождения. В программе ниже мы защитили проверку условия мьютексом и под его защитой ждем скрипку и смычок порознь. Если скрипка или смычок заняты, поток засыпает. Он не владеет мьютексом до тех пор, пока другой поток не просигнализирует о том, что ресурс свободен. В этот момент первый поток просыпается и снова захватывает мьютекс.

Код представлен в листинге 13.4.

Листинг 13.4. Три скрипача

require 'thread'
@music = Mutex.new
@violin = ConditionVariable.new
@bow = ConditionVariable.new
@violins_free = 2
@bows_free = 1
def musician(n)
 loop do
  sleep rand(0)
  @music.synchronize do
   @violin.wait(@music) while @violins_frее == 0
   @violins_free -= 1
   puts "#{n} владеет скрипкой"
   puts "скрипок #@violins_frее, смычков #@bows_free"
   @bow.wait(@music) while @bows_free == 0
   @bows_free -= 1
   puts "#{n} владеет смычком"
   puts "скрипок #@violins_free, смычков #@bows_free"
  end
  sleep rand(0)
  puts "#{n}: (...играет...)"
  sleep rand(0)
  puts "#{n}: Я закончил."
  @music.synchronize do
   @violins_free += 1
   @violin.signal if @violins_free == 1
   @bows_free += 1
   @bow.signal if @bows_free == 1
  end
 end
end
threads = []
3.times {|i| threads << Thread.new { musician(i) } }
threads.each {|t| t.join }

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

Оглавление книги


Генерация: 2.481. Запросов К БД/Cache: 2 / 0
поделиться
Вверх Вниз