logo
Программирование на языке Ruby

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