Книга: Программирование на языке 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 внутри потока, то в ней мог бы находиться любой код, как, впрочем, и всегда.

Оглавление книги


Генерация: 0.065. Запросов К БД/Cache: 0 / 0
поделиться
Вверх Вниз