В обоих случаях первое значение было отправлено, как и следовало ожидать. Однако два типа каналов начинают различаться, когда отправляется второе значение. В случае без буферизации ожидающий получатель отсутствует, поэтому канал запускает перепланирование, в результате чего выполнение переключается обратно на основное волокно. После печати первых двух значений выполнение переключается обратно на волокно и отправляет третье значение. Это приводит к перепланированию, при котором выполнение будет переключено обратно на основное волокно, когда в следующий раз появится такая возможность. В данном конкретном случае такой шанс появляется после печати конечного сообщения и когда в волокне больше нечего выполнять.
В случае с буферизацией первое отправленное значение выполняет команду channel.receive
, которая первоначально вызвала выполнение оптоволокна. В буфер добавляется второе значение, за ним следует третье значение и, наконец, конечное сообщение. На этом этапе волокно завершает выполнение, поэтому выполнение переключается обратно на основное волокно, печатая все три значения: они включают одно из начального приема плюс два из буфера канала. Давайте добавим еще одно значение к волокну, добавив puts “Before send 4”
и channel.send 4
перед конечным сообщением. Затем обновите цикл, чтобы сказать 4.times do
. Повторный запуск программы дает следующий результат:
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"