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
puts "неправильна."
end
13.1.5. Обработка исключений
Что произойдет, если в потоке возникнет исключение? Как выясняется, поведение можно сконфигурировать заранее.
Существует флаг abort_on_exception
, который работает как на уровне класса, так и на уровне экземпляра. Он реализован в виде метода доступа (то есть позволяет читать и устанавливать атрибут) на обоих уровнях. Если abort_on_exception
для некоторого потока равен true
, то при возникновении в этом потоке исключения будут завершены и все остальные потоки.
Thread.abort_on_exception = true
t1 = Thread.new do
puts "Привет!"
sleep 2
raise "some exception"
puts "Пока!"
end
t2 = Thread.new { sleep 100 }
sleep 2
puts "Конец"
В этом примере флаг abort_on_exception
установлен в true
на уровне системы в целом (отменяя подразумеваемое по умолчанию значение). Следовательно, когда в потоке t1
возникает исключение, завершаются и t1
, и главный поток. Печатается только слово «Привет!».
В следующем примере эффект такой же:
t1 = Thread.new do
puts "Привет!"
sleep 2
raise "some exception"
puts "Пока!"
end
t1.abort_on_exception = true
t2 = Thread.new { sleep 100 }
sleep 2
puts "Конец"
А вот в следующем оставлено принимаемое по умолчанию значение false
, и мы наконец-то видим слово «Конец», печатаемое главным потоком (слова «Пока!» мы не увидим никогда, поскольку поток t1
при возникновении исключения завершается безусловно).
t1 = Thread.new do
puts "Привет!"
sleep 2
raise "some exception"
puts "Пока!"
end
t2 = Thread.new { sleep 100 }
sleep 2
puts "Конец"
# Выводится:
Привет!
Конец
Группа потоков — это механизм управления логически связанными потоками. По умолчанию все потоки принадлежат группе Default
(это константа класса). Но если создать новую группу, то в нее можно будет помещать потоки.
В любой момент времени поток может принадлежать только одной группе. Если поток помещается в группу, то он автоматически удаляется из той группы, которой принадлежал ранее.
Читать дальше
Конец ознакомительного отрывка
Купить книгу