t1.join
t2.join
В этом искусственном примере вызов Thread.pass
приводит к печати строк в следующем порядке: alpha gamma delta beta
. Без него было бы напечатано alpha beta gamma delta
. Конечно, этот механизм следует использовать не для синхронизации, а только для экономного расходования процессорного времени.
Выполнение приостановленного потока можно возобновить методами методами run
или wakeup
:
t1 = Thread.new do
Thread.stop
puts 'Здесь есть изумруд.'
end
t2 = Thread.new do
Thread.stop
puts 'Вы находитесь в точке Y2.'
end
sleep 1
t1.wakeup
t2.run
Между этими методами есть тонкое различие. Метод wakeup
изменяет состояние потока, так что он становится готовым к выполнению, но не запускает его немедленно. Метод же run
пробуждает поток и сразу же планирует его выполнение.
В данном случае t1
просыпается раньше t2
, но t2
планируется первым, что приводит к следующему результату:
Вы находитесь в точке Y2.
Здесь есть изумруд.
Конечно, было бы неосмотрительно реализовывать синхронизацию на основе этого механизма.
Метод экземпляра raise
возбуждает исключение в потоке, от имени которого вызван. (Этот метод необязательно вызывать в том потоке, которому адресовано исключение.)
factorial1000 = Thread.new do
begin
prod = 1
1.upto(1000) {|n| prod *= n }
puts '1000! = #{prod}'
rescue
# Ничего не делать...
end
end
sleep 0.01 # На вашей машине значение может быть иным.
if factorial1000.alive?
factorial1000.raise('Стоп!')
puts 'Вычисление было прервано!'
else
puts 'Вычисление успешно завершено.'
end
Поток, запущенный в предыдущем примере, пытался вычислить факториал 1000. Если для этого не хватило одной сотой секунды, то главный поток завершит его. Как следствие, на относительно медленной машине будет напечатано сообщение «Вычисление было прервано!» Что касается части rescue
внутри потока, то в ней мог бы находиться любой код, как, впрочем, и всегда.
13.1.4. Назначение рандеву (и получение возвращенного значения)
Иногда главный поток хочет дождаться завершения другого потока. Для этой цели предназначен метод join
:
t1 = Thread.new { do_something_long() }
do_something_brief()
t1.join # Ждать завершения t1.
Отметим, что вызывать метод join
необходимо, если нужно дождаться завершения другого потока. В противном случае главный поток завершится, а вместе с ним и все остальные. Например, следующий код никогда не напечатал бы окончательный ответ, не будь в конце вызова join
:
meaning_of_life = Thread.new do
puts 'Смысл жизни заключается в...'
sleep 10
puts 42
end
sleep 9
meaning_of_life.join
Существует полезная идиома, позволяющая вызвать метод join
для всех «живых» потоков, кроме главного (ни один поток, даже главный, не может вызывать join
для самого себя).
Thread.list.each { |t| t.join if t != Thread.main }
Конечно, любой поток, а не только главный, может вызвать join
для любого другого потока. Если главный поток и какой-то другой попытаются вызвать join
друг для друга, возникнет тупиковая ситуация. Интерпретатор обнаружит это и завершит программу.
thr = Thread.new { sleep 1; Thread.main.join }
thr.join # Тупиковая ситуация!
С потоком связан блок, который может возвращать значение. Следовательно, и сам поток может возвращать значение. Метод value
неявно вызывает join
и ждет, пока указанный поток завершится, а потом возвращает значение последнего вычисленного в потоке выражения.
max = 10000
thr = Thread.new do
sum = 0
1.upto(max) { |i| sum += i }
sum
end
guess = (max*(max+1))/2
print 'Формула '
if guess == thr.value
puts 'правильна.'
else