Хэл Фултон - Программирование на языке Ruby
- Название:Программирование на языке Ruby
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2007
- Город:Москва
- ISBN:5-94074-357-9
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Хэл Фултон - Программирование на языке Ruby краткое содержание
Ruby — относительно новый объектно-ориентированный язык, разработанный Юкихиро Мацумото в 1995 году и позаимствовавший некоторые особенности у языков LISP, Smalltalk, Perl, CLU и других. Язык активно развивается и применяется в самых разных областях: от системного администрирования до разработки сложных динамических сайтов.
Книга является полноценным руководством по Ruby — ее можно использовать и как учебник, и как справочник, и как сборник ответов на вопросы типа «как сделать то или иное в Ruby». В ней приведено свыше 400 примеров, разбитых по различным аспектам программирования, и к которым автор дает обстоятельные комментарии.
Издание предназначено для программистов самого широкого круга и самой разной квалификации, желающих научиться качественно и профессионально работать на Ruby.
Программирование на языке Ruby - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
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 "Конец"
# Выводится:
Привет!
Конец
13.1.6. Группы потоков
Группа потоков — это механизм управления логически связанными потоками. По умолчанию все потоки принадлежат группе Default
(это константа класса). Но если создать новую группу, то в нее можно будет помещать потоки.
В любой момент времени поток может принадлежать только одной группе. Если поток помещается в группу, то он автоматически удаляется из той группы, которой принадлежал ранее.
Метод класса ThreadGroup.new
создает новую группу потоков, а метод экземпляра add
помещает поток в группу.
f1 = Thread.new("file1") { |file| waitfor(file) }
f2 = Thread.new("file2") { |file| waitfor(file) }
file_threads = ThreadGroup.new
file_threads.add f1
file_threads.add f2
Метод экземпляра list
возвращает массив всех потоков, принадлежащих данной группе.
# Подсчитать все "живые" потоки в группе this_group.
count = 0
this_group.list.each {|x| count += 1 if x.alive? }
if count < this_group.list.size
puts "Некоторые потоки в группе this_group уже скончались."
else
puts "Все потоки в группе this_group живы."
end
В класс ThreadGroup
можно добавить немало полезных методов. В примере ниже показаны методы для возобновления всех потоков, принадлежащих группе, для группового ожидания потоков (с помощью join
) и для группового завершения потоков:
class ThreadGroup
def wakeup
list.each { |t| t.wakeup }
end
def join
list.each { |t| t.join if t != Thread.current }
end
def kill
list.each { |t| t.kill }
end
end
13.2. Синхронизация потоков
Почему необходима синхронизация? Потому что из-за «чередования» операций доступ к переменным и другим сущностям может осуществляться в порядке, который не удается установить путем чтения исходного текста отдельных потоков. Два и более потоков, обращающихся к одной и той же переменной, могут взаимодействовать между собой непредвиденными способами, и отлаживать такую программу очень трудно.
Рассмотрим простой пример:
x = 0
t1 = Thread.new do
1.upto(1000) do
x = x + 1
end
end
t2 = Thread.new do
1.upto(1000) do
x = x + 1
end
end
t1.join
t2.join
puts x
Сначала переменная x
равна 0. Каждый поток увеличивает ее значение на тысячу раз. Логика подсказывает, что в конце должно быть напечатано 2000.
Но фактический результат противоречит логике. На конкретной машине было напечатано значение 1044. В чем дело?
Мы предполагали, что инкремент целого числа — атомарная (неделимая) операция. Но это не так. Рассмотрим последовательность выполнения приведенной выше программы. Поместим поток t1
слева, а поток t2
справа. Каждый квант времени занимает одну строчку и предполагается, что к моменту, когда был сделан этот мгновенный снимок, переменная x
имела значение 123.
t1 t2
-------------------------- -----------------------------
Прочитать значение x (123)
Прочитать значение x (123)
Увеличить значение на 1 (124)
Увеличить значение на 1 (124)
Записать результат в x
Записать результат в x
Ясно, что каждый поток увеличивает на 1 то значение, которое видит. Но не менее ясно и то, что после увеличения на 1 обоими потоками x
оказалось равно всего 124.
И это лишь самая простая из проблем, возникающих в связи с синхронизацией. Для решения более сложных приходится прилагать серьезные усилия — это предмет изучения специалистами в области теоретической информатики и математики.
13.2.1. Синхронизация с помощью критических секций
Простейший способ синхронизации дают критические секции. Когда поток входит в критическую секцию программы, гарантируется, что никакой другой поток не войдет в нее, пока первый не выйдет.
Если акцессору Thread.critical
присвоить значение true
, то выполнение других потоков не будет планироваться. В следующем примере мы переработали код предыдущего, воспользовавшись акцессором critical
для определения критической области, которая защищает уязвимые участки программы.
x = 0
t1 = Thread.new do
1.upto(1000) do
Thread.critical = true
x = x + 1
Thread.critical = false
end
end
t2 = Thread.new do
1.upto(1000) do
Thread.critical = true
x = x + 1
Thread.critical = false
end
end
t1.join
t2.join
puts x
Теперь последовательность выполнения изменилась; взгляните, в каком порядке работают потоки t1
и t2
. (Конечно, вне того участка, где происходит увеличение переменной, потоки могут чередоваться более-менее случайным образом.)
t1 t2
----------------------------- -----------------------------
Прочитать значение x (123)
Увеличить значение на 1 (124)
Записать результат в x
Интервал:
Закладка: