Programming

섬유질이 필요한 이유

procodes 2020. 8. 27. 22:15
반응형

섬유질이 필요한 이유


Fibers의 경우 전형적인 예가 있습니다 : 피보나치 수 생성

fib = Fiber.new do  
  x, y = 0, 1 
  loop do  
    Fiber.yield y 
    x,y = y,x+y 
  end 
end

여기에 섬유가 필요한 이유는 무엇입니까? 동일한 Proc로 이것을 다시 작성할 수 있습니다 (실제로는 클로저)

def clsr
  x, y = 0, 1
  Proc.new do
    x, y = y, x + y
    x
  end
end

그래서

10.times { puts fib.resume }

prc = clsr 
10.times { puts prc.call }

동일한 결과를 반환합니다.

그래서 섬유의 장점은 무엇입니까? 람다 및 기타 멋진 Ruby 기능으로는 할 수없는 Fibers로 어떤 종류의 작업을 작성할 수 있습니까?


Fiber는 아마도 애플리케이션 레벨 코드에서 직접 사용하지 않을 것입니다. 그것들은 다른 추상화를 만드는 데 사용할 수있는 흐름 제어 기본 요소이며, 그런 다음 상위 수준 코드에서 사용할 수 있습니다.

아마도 Ruby에서 섬유를 가장 많이 사용 Enumerator하는 것은 Ruby 1.9의 핵심 Ruby 클래스 인 s 를 구현 하는 것입니다. 이것들은 매우 유용합니다.

Ruby 1.9에서는 블록 전달 하지 않고 코어 클래스에서 거의 모든 반복기 메서드를 호출 하면 Enumerator.

irb(main):001:0> [1,2,3].reverse_each
=> #<Enumerator: [1, 2, 3]:reverse_each>
irb(main):002:0> "abc".chars
=> #<Enumerator: "abc":chars>
irb(main):003:0> 1.upto(10)
=> #<Enumerator: 1:upto(10)>

이들은 EnumeratorEnumerable 객체이며, 그 each메소드는 블록으로 호출 된 경우 원래 반복기 메소드에 의해 생성되었을 요소를 생성합니다. 방금 준 예제에서 반환 된 Enumerator reverse_each에는 each3,2,1을 산출 하는 메서드가 있습니다. 반환 된 열거 chars자는 "c", "b", "a"등 산출합니다. 그러나 원래 반복기 메서드와 달리 Enumerator는 next반복적으로 호출하면 요소를 하나씩 반환 할 수도 있습니다 .

irb(main):001:0> e = "abc".chars
=> #<Enumerator: "abc":chars>
irb(main):002:0> e.next
=> "a"
irb(main):003:0> e.next
=> "b"
irb(main):004:0> e.next
=> "c"

"내부 반복자"와 "외부 반복자"에 대해 들어 보셨을 것입니다 (둘 다에 대한 좋은 설명은 "Gang of Four"디자인 패턴 책에 나와 있습니다). 위의 예는 열거자를 사용하여 내부 반복기를 외부 반복기로 바꿀 수 있음을 보여줍니다.

이것은 고유 한 열거자를 만드는 한 가지 방법입니다.

class SomeClass
  def an_iterator
    # note the 'return enum_for...' pattern; it's very useful
    # enum_for is an Object method
    # so even for iterators which don't return an Enumerator when called
    #   with no block, you can easily get one by calling 'enum_for'
    return enum_for(:an_iterator) if not block_given?
    yield 1
    yield 2
    yield 3
  end
end

해 봅시다:

e = SomeClass.new.an_iterator
e.next  # => 1
e.next  # => 2
e.next  # => 3

잠깐만 요 ... 뭔가 이상하게 보이나요? 당신은 썼다 yield에서 문을 an_iterator직선 코드로,하지만 열거 그들에게 실행할 수 있습니다 한 번에 하나씩 . 에 대한 호출 사이 next에의 실행 an_iterator이 "고정"됩니다. 를 호출 할 때마다 next다음 yield문으로 계속 실행 된 다음 다시 "멈 춥니 다".

Can you guess how this is implemented? The Enumerator wraps the call to an_iterator in a fiber, and passes a block which suspends the fiber. So every time an_iterator yields to the block, the fiber which it is running on is suspended, and execution continues on the main thread. Next time you call next, it passes control to the fiber, the block returns, and an_iterator continues where it left off.

It would be instructive to think of what would be required to do this without fibers. EVERY class which wanted to provide both internal and external iterators would have to contain explicit code to keep track of state between calls to next. Each call to next would have to check that state, and update it before returning a value. With fibers, we can automatically convert any internal iterator to an external one.

This doesn't have to do with fibers persay, but let me mention one more thing you can do with Enumerators: they allow you to apply higher-order Enumerable methods to other iterators other than each. Think about it: normally all the Enumerable methods, including map, select, include?, inject, and so on, all work on the elements yielded by each. But what if an object has other iterators other than each?

irb(main):001:0> "Hello".chars.select { |c| c =~ /[A-Z]/ }
=> ["H"]
irb(main):002:0> "Hello".bytes.sort
=> [72, 101, 108, 108, 111]

Calling the iterator with no block returns an Enumerator, and then you can call other Enumerable methods on that.

Getting back to fibers, have you used the take method from Enumerable?

class InfiniteSeries
  include Enumerable
  def each
    i = 0
    loop { yield(i += 1) }
  end
end

If anything calls that each method, it looks like it should never return, right? Check this out:

InfiniteSeries.new.take(10) # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

I don't know if this uses fibers under the hood, but it could. Fibers can be used to implement infinite lists and lazy evaluation of a series. For an example of some lazy methods defined with Enumerators, I have defined some here: https://github.com/alexdowad/showcase/blob/master/ruby-core/collections.rb

You can also build a general-purpose coroutine facility using fibers. I've never used coroutines in any of my programs yet, but it's a good concept to know.

I hope this gives you some idea of the possibilities. As I said at the beginning, fibers are a low-level flow-control primitive. They make it possible to maintain multiple control-flow "positions" within your program (like different "bookmarks" in the pages of a book) and switch between them as desired. Since arbitrary code can run in a fiber, you can call into 3rd-party code on a fiber, and then "freeze" it and continue doing something else when it calls back into code you control.

Imagine something like this: you are writing a server program which will service many clients. A complete interaction with a client involves going through a series of steps, but each connection is transient, and you have to remember state for each client between connections. (Sound like web programming?)

Rather than explicitly storing that state, and checking it each time a client connects (to see what the next "step" they have to do is), you could maintain a fiber for each client. After identifying the client, you would retrieve their fiber and re-start it. Then at the end of each connection, you would suspend the fiber and store it again. This way, you could write straight-line code to implement all the logic for a complete interaction, including all the steps (just as you naturally would if your program was made to run locally).

I'm sure there's many reasons why such a thing may not be practical (at least for now), but again I'm just trying to show you some of the possibilities. Who knows; once you get the concept, you may come up with some totally new application which no-one else has thought of yet!


Unlike closures, which have a defined entry and exit point, fibers can preserve their state and return (yield) many times:

f = Fiber.new do
  puts 'some code'
  param = Fiber.yield 'return' # sent parameter, received parameter
  puts "received param: #{param}"
  Fiber.yield #nothing sent, nothing received 
  puts 'etc'
end

puts f.resume
f.resume 'param'
f.resume

prints this:

some code
return
received param: param
etc

Implementation of this logic with other ruby features will be less readable.

With this feature, good fibers usage is to do manual cooperative scheduling (as Threads replacement). Ilya Grigorik has a good example on how to turn an asynchronous library (eventmachine in this case) into what looks like a synchronous API without losing the advantages of IO-scheduling of the asynchronous execution. Here is the link.

참고URL : https://stackoverflow.com/questions/9052621/why-do-we-need-fibers

반응형