13.1.3. Опрос и изменение состояния потока
13.1.3. Опрос и изменение состояния потока
В классе Thread есть несколько полезных методов класса. Метод list возвращает массив «живых» потоков, метод main возвращает ссылку на главный поток программы, который породил все остальные, а метод current позволяет потоку идентифицировать самого себя.
t1 = Thread.new { sleep 100 }
t2 = Thread.new do
if Thread.current == Thread.main
puts "Это главный поток." # HE печатается,
end
1.upto(1000) { sleep 0.1 }
end
count = Thread.list.size # 3
if Thread.list.include ?(Thread.main)
puts "Главный поток жив." # Печатается всегда!
end
if Thread.current == Thread.main
puts "Я главный поток." # Здесь печатается...
end
Методы exit, pass, start, stop и kill служат для управления выполнением потоков (как изнутри, так и извне):
# в главном потоке...
Thread.kill(t1) # Завершить этот поток.
Thread.pass # Передать управление t2.
t3 = Thread.new do
sleep 20
Thread.exit # Выйти из потока.
puts "Так не бывает!" # Никогда не выполняется.
end
Thread.kill(t2) # Завершить t2.
# Выйти из главного потока (все остальные тоже завершаются).
Thread.exit
Отметим, что не существует метода экземпляра stop, поэтому поток может приостановить собственное выполнение, но не выполнение другого потока.
Существуют различные методы для опроса состояния потока. Метод экземпляра alive? сообщает, является ли данный поток «живым» (не завершил выполнение), а метод stop? — находится ли он в состоянии «приостановлен».
count = 0
t1 = Thread.new { loop { count += 1 } }
t2 = Thread.new { Thread.stop }
sleep 1
flags = [t1.alive?, # true
t1.stop?, # false
t2.alive?, # true
t2.stop?] # true
Получить состояние потока позволяет метод status. Он возвращает значение "run", если поток выполняется; "sleep" — если он приостановлен, спит или ожидает результата ввода/вывода; false — если поток нормально завершился, и nil — если поток завершился в результате исключения.
t1 = Thread.new { loop {} }
t2 = Thread.new { sleep 5 }
t3 = Thread.new { Thread.stop }
t4 = Thread.new { Thread.exit }
t5 = Thread.new { raise "exception" }
s1 = t1.status # "run"
s2 = t2.status # "sleep"
s3 = t3.status # "sleep"
s4 = t4.status # false
s5 = t5.status # nil
Глобальную переменную $SAFE можно установить по-разному в разных потоках. Стало быть, она вовсе не является глобальной, но стоит ли жаловаться на это, если она позволяет разным потокам работать с разным уровнем безопасности? Метод safe_level возвращает текущий уровень безопасности потока.
t1 = Thread.new { $SAFE = 1; sleep 5 }
t2 = Thread.new { $SAFE = 3; sleep 5 }
sleep 1
lev0 = Thread.main.safe_level # 0
lev1 = t1.safe_level # 1
lev2 = t2.safe_level # 3
Метод доступа priority позволяет узнать и изменить приоритет потока:
t1 = Thread.new { loop { sleep 1 } }
t2 = Thread.new { loop { sleep 1 } }
t2.priority = 3 # Установить для потока t2 приоритет 3
p1 = t1.priority # 0
p2 = t2.priority # 3
Поток с большим приоритетом будет чаще получать процессорное время. Специальный метод pass позволяет передать управление планировщику. Иными словами, поток просто уступает свой временной квант, но не приостанавливается и не засыпает.
t1 = Thread.new do
puts "alpha"
Thread.pass
puts "beta"
end
t2 = Thread.new do
puts "gamma"
puts "delta"
end
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 внутри потока, то в ней мог бы находиться любой код, как, впрочем, и всегда.
Данный текст является ознакомительным фрагментом.