Книга: Программирование на языке Ruby
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
внутри потока, то в ней мог бы находиться любой код, как, впрочем, и всегда.
- 3.2.4. Ошибки при задавании вопросов
- Вопросы и ответы
- Контрольные вопросы
- Добавление, изменение и удаление элементов списка
- Добавление, изменение и удаление столбцов списка
- Добавление и изменение представления списка
- Скрипт «Опрос об удовлетворенности клиента после сделки»
- Когда следует задавать проясняющие вопросы
- 7.8. Специальные вопросы
- Вопросы и задания
- Часть II Изменение способов ведения бизнеса
- Изменение состояния виртуальной машины