Para Fibras, temos um exemplo clássico: geração de números de Fibonacci
fib = Fiber.new do
x, y = 0, 1
loop do
Fiber.yield y
x,y = y,x+y
end
end
Por que precisamos de fibras aqui? Posso reescrever isso apenas com o mesmo Proc (encerramento, na verdade)
def clsr
x, y = 0, 1
Proc.new do
x, y = y, x + y
x
end
end
assim
10.times { puts fib.resume }
e
prc = clsr
10.times { puts prc.call }
retornará apenas o mesmo resultado.
Então, quais são as vantagens das fibras. Que tipo de coisa eu posso escrever com Fibers que não consigo fazer com lambdas e outros recursos interessantes do Ruby?
Respostas:
Fibras são algo que você provavelmente nunca usará diretamente no código de nível de aplicativo. Eles são um primitivo de controle de fluxo que você pode usar para construir outras abstrações, que você usa em código de nível superior.
Provavelmente, o uso nº 1 de fibras em Ruby é implementar
Enumerator
s, que são uma classe central de Ruby no Ruby 1.9. Eles são incrivelmente úteis.No Ruby 1.9, se você chamar quase qualquer método iterador nas classes principais, sem passar um bloco, ele retornará um
Enumerator
.Esses
Enumerator
s são objetos Enumerable, e seuseach
métodos geram os elementos que teriam sido produzidos pelo método iterador original, se tivesse sido chamado com um bloco. No exemplo que acabei de dar, o Enumerador retornado porreverse_each
tem umeach
método que retorna 3,2,1. O Enumerador retornado porchars
retorna "c", "b", "a" (e assim por diante). MAS, ao contrário do método iterador original, o Enumerator também pode retornar os elementos um por um se você chamánext
-lo repetidamente:Você pode ter ouvido falar de "iteradores internos" e "iteradores externos" (uma boa descrição de ambos é fornecida no livro "Gang of Four" Design Patterns). O exemplo acima mostra que Enumeradores podem ser usados para transformar um iterador interno em um externo.
Esta é uma maneira de fazer seus próprios enumeradores:
Vamos tentar:
Espere um minuto ... alguma coisa parece estranha aí? Você escreveu as
yield
instruçõesan_iterator
como código de linha reta, mas o Enumerador pode executá-las uma de cada vez . Entre as chamadas paranext
, a execução dean_iterator
é "congelada". Cada vez que você chamanext
, ele continua executando a seguinteyield
instrução e, em seguida, "congela" novamente.Você consegue adivinhar como isso é implementado? O Enumerador envolve a chamada para
an_iterator
em uma fibra e passa um bloco que suspende a fibra . Portanto, toda vez quean_iterator
cede ao bloco, a fibra na qual ele está sendo executado é suspensa e a execução continua no thread principal. Na próxima vez que você ligarnext
, ele passa o controle para a fibra, o bloco retorna ean_iterator
continua de onde parou.Seria instrutivo pensar no que seria necessário para fazer isso sem fibras. CADA classe que quisesse fornecer iteradores internos e externos teria que conter código explícito para controlar o estado entre as chamadas para
next
. Cada chamada para next teria que verificar esse estado e atualizá-lo antes de retornar um valor. Com fibras, podemos converter automaticamente qualquer iterador interno em externo.Isso não tem a ver com fibras persay, mas deixe-me mencionar mais uma coisa que você pode fazer com Enumerators: eles permitem que você aplique métodos Enumerable de ordem superior a outros iteradores diferentes de
each
. Pense nisso: normalmente todos os métodos Enumerable, incluindomap
,select
,include?
,inject
, e assim por diante, todos os trabalhos sobre os elementos gerados peloeach
. Mas e se um objeto tiver outros iteradores diferentes deeach
?Chamar o iterador sem bloco retorna um Enumerator, e então você pode chamar outros métodos Enumerable nele.
Voltando às fibras, você usou o
take
método de Enumerable?Se alguma coisa chamar esse
each
método, parece que nunca deve retornar, certo? Veja isso:Não sei se isso usa fibras sob o capô, mas poderia. As fibras podem ser usadas para implementar listas infinitas e avaliação preguiçosa de uma série. Para obter um exemplo de alguns métodos preguiçosos definidos com Enumeradores, defini alguns aqui: https://github.com/alexdowad/showcase/blob/master/ruby-core/collections.rb
Você também pode construir uma instalação de co-rotina de uso geral usando fibras. Eu nunca usei corrotinas em nenhum dos meus programas ainda, mas é um bom conceito de saber.
Espero que isso lhe dê uma ideia das possibilidades. Como eu disse no início, as fibras são um primitivo de controle de fluxo de baixo nível. Eles tornam possível manter várias "posições" de fluxo de controle dentro de seu programa (como diferentes "marcadores" nas páginas de um livro) e alternar entre eles conforme desejado. Como o código arbitrário pode ser executado em uma fibra, você pode chamar o código de terceiros em uma fibra e, em seguida, "congelá-lo" e continuar fazendo outra coisa quando ele retornar ao código que você controla.
Imagine algo assim: você está escrevendo um programa de servidor que atenderá a muitos clientes. Uma interação completa com um cliente envolve passar por uma série de etapas, mas cada conexão é transitória e você deve lembrar o estado de cada cliente entre as conexões. (Parece programação da web?)
Em vez de armazenar explicitamente esse estado e verificá-lo cada vez que um cliente se conecta (para ver qual é a próxima "etapa" que eles precisam fazer), você pode manter uma fibra para cada cliente. Depois de identificar o cliente, você deve recuperar sua fibra e reiniciá-lo. Então, ao final de cada conexão, você suspenderia a fibra e a armazenaria novamente. Dessa forma, você poderia escrever código em linha reta para implementar toda a lógica para uma interação completa, incluindo todas as etapas (exatamente como você faria naturalmente se seu programa fosse feito para ser executado localmente).
Tenho certeza de que há muitos motivos pelos quais tal coisa pode não ser prática (pelo menos por agora), mas, novamente, estou apenas tentando mostrar a você algumas das possibilidades. Quem sabe; depois de entender o conceito, você pode criar um aplicativo totalmente novo no qual ninguém mais pensou ainda!
fonte
chars
ou outros enumeradores com apenas encerramentos?Enumerable
irá incluir alguns métodos "preguiçosos" no Ruby 2.0.take
não requer fibra. Em vez disso,take
simplesmente interrompe durante o n-ésimo rendimento. Quando usado dentro de um bloco,break
retorna o controle para o quadro que define o bloco.a = [] ; InfiniteSeries.new.each { |x| a << x ; break if a.length == 10 } ; a
Ao contrário dos fechamentos, que têm um ponto de entrada e saída definido, as fibras podem preservar seu estado e retornar (escoamento) muitas vezes:
imprime isto:
A implementação desta lógica com outros recursos do ruby será menos legível.
Com esse recurso, um bom uso de fibras é fazer o agendamento cooperativo manual (como substituição de Threads). Ilya Grigorik tem um bom exemplo de como transformar uma biblioteca assíncrona (
eventmachine
neste caso) no que parece ser uma API síncrona sem perder as vantagens do agendamento IO da execução assíncrona. Aqui está o link .fonte
physical meaning
em um exemplo mais simples