item += 1
end
end
consumer = Thread.new do
loop do
sleep (rand 0)+0.9
item = buffer.deq
puts 'Потребитель потребил #{item}'
puts ' ожидает = #{buffer.num_waiting}'
end
end
sleep 60 # Работать одну минуту, потом завершить оба потока.
Чтобы поместить элемент в очередь и извлечь из нее, рекомендуется применять соответственно методы enq
и deq
. Можно было бы для помещения в очередь пользоваться также методом push
, а для извлечения — методами pop
и shift
, но их названия не так мнемоничны в применении к очередям.
Метод empty?
проверяет, пуста ли очередь, а метод clear
опустошает ее. Метод size
(и его синоним length
) возвращает число элементов в очереди.
# Предполагается, что другие потоки не мешают...
buff = Queue.new
buff.enq 'one'
buff.enq 'two'
buff.enq 'three'
n1 = buff.size # 3
flag1 = buff.empty? # false
buff.clear
n2 = buff.size # 0
flag2 = buff.empty? # true
Метод num_waiting
возвращает число потоков, ожидающих доступа к очереди. Если размер очереди не ограничен, то это потоки, ожидающие возможности удалить элементы; для ограниченной очереди включаются также потоки, пытающиеся добавить элементы.
Необязательный параметр non_block
метода deq
в классе Queue
по умолчанию равен false
. Если же он равен true
, по при попытке извлечь элемент из пустой очереди он не блокирует поток, а возбуждает исключение ThreadError
.
13.2.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