Before send 1
Before send 2
Before send 3
Before send 4
1
2
3
4
Обратите внимание, что на этот раз конечное сообщение не было напечатано. Это связано с тем, что второе и третье значения укладываются в размер буфера, равный 2. Однако, когда отправляется четвертое значение, буфер больше не может обрабатывать дополнительные значения, поэтому канал запускает перепланирование, в результате чего выполнение переключается на основное волокно снова. Поскольку первое значение было отправлено как часть исходного канала channel.recieve, а второе, третье и четвертое значения уже находятся в буфере канала, они печатаются так, как и следовало ожидать. Однако к этому моменту основное волокно уже получило четыре желаемых значения. Поэтому у него никогда не будет возможности возобновить выполнение волокна, чтобы распечатать конечное сообщение.
Во всех этих примерах мы получали значение из одного канала. Но что, если вы хотите использовать первые значения, полученные из набора из нескольких каналов? Здесь в игру вступает ключевое слово select (не путать с методом #select). Ключевое слово select позволяет вам ожидать на нескольких каналах и выполнять некоторую логику в зависимости от того, какой из них получит значение первым. Кроме того, он поддерживает работу логики, если все каналы заблокированы и по истечении заданного периода времени значение не получено. Начнем с простого примера:
channel1 = Channel(Int32).new
channel2 = Channel(Int32).new
spawn do
puts "Starting fiber 1"
sleep 3
channel1.send 1
end
spawn do
puts "Starting fiber 2"
sleep 1
channel2.send 2
end
select
when v = channel1.receive
puts "Received #{v} from channel1"
when v = channel2.receive
puts "Received #{v} from channel2"
end
Этот пример выводит следующее:
Starting fiber 1
Starting fiber 2
Received 2 from channel2
Здесь оба волокна начинают выполняться более или менее одновременно, но поскольку у второго волокна более короткий период сна и он завершается первым, это приводит к тому, что ключевое слово select печатает значение из этого канала и затем завершает работу. Обратите внимание, что ключевое слово select действует аналогично одиночному каналу channel.receive в том смысле, что оно блокирует основное волокно, а затем продолжает работу после получения значения из любого канала. Кроме того, мы могли бы обрабатывать несколько итераций, поместив ключевое слово select в цикл вместе с методом timeout, чтобы избежать вечной блокировки. Давайте расширим предыдущий пример, чтобы продемонстрировать, как это работает. Во-первых, давайте добавим переменную channel3, аналогичную двум другим, которые у нас уже есть. Далее давайте создадим еще одно волокно, которое отправит значение в наш третий канал. Например, взгляните на следующее:
spawn do
puts "Starting fiber 3"
channel3.send 3
end
Наконец, мы можем переместить наше ключевое слово select в цикл:
loop do
select
when v = channel1.receive
puts "Received #{v} from channel1"
when v = channel2.receive
puts "Received #{v} from channel2"
when v = channel3.receive
puts "Received #{v} from channel3"
when timeout 3.seconds
puts "Nothing left to process, breaking out"
break
end
end
Эта версия ключевого слова select аналогична первой, но мы добавили к ней два новых предложения. Один считывает значение из третьего канала, а другой выйдет из цикла, если ни по одному каналу в течение трех секунд не будут получены данные. Вывод этой программы следующий:
Starting fiber 1
Starting fiber 2
Starting fiber 3
Received 3 from channel3
Received 2 from channel2
Received 1 from channel1
Nothing left to process, breaking out
Волокна начинают работать по порядку, но заканчивают в другом порядке из-за разной продолжительности сна. Через три секунды выполняется последнее предложение if, поскольку ничего не получено, а затем программа завершает работу.