Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the point of Fibers in Ruby?

I don't understand how the below:

counts = Hash.new(0)

File.foreach("testfile") do |line|
  line.scan(/\w+/) do |word|
    word = word.downcase
    counts[word] += 1
  end
end

counts.keys.sort.each {|k| print "#{k}:#{counts[k]} "}

Is so much worse than:

words = Fiber.new do
  File.foreach("testfile") do |line|
    line.scan(/\w+/) do |word|
      Fiber.yield word.downcase
    end
  end
end

counts = Hash.new(0)

while word = words.resume
  counts[word] += 1
end

counts.keys.sort.each {|k| print "#{k}:#{counts[k]} "}
like image 606
Senjai Avatar asked May 20 '13 20:05

Senjai


People also ask

What are fibers in Ruby?

Initially released in Ruby 1.9 as a coroutine abstraction, fibers have never really become mainstream. Ruby 3 now changes the game by introducing the Fiber::SchedulerInterface class which allows for quick and more efficient context switching when compared to its Thread counterpart.

What is fiber in Ruby on Rails?

One of the major focuses for Ruby 3 was parallelism and concurrency. A trailblazer in this space is the use of Ruby Fibers. Fibers are a concurrency mechanism that allows us to pause, loop, and resume execution while consuming far fewer context switches.

Is Ruby concurrent?

In particular, Ruby concurrency is when two tasks can start, run, and complete in overlapping time periods. It doesn't necessarily mean, though, that they'll ever both be running at the same instant (e.g., multiple threads on a single-core machine).


1 Answers

Fibers are a way of suspending and resuming arbitrary blocks of code. An example like this isn't really a great use-case as it doesn't offer any real advantage over the traditional way of reading in lines and processing them.

In this particular example, if you wanted to make it better, you'd write an enumerator-style interface so you could write:

words = WordsReader.new("testfile")

words.each do |word|
  # ...
end

Where Fibers become important is in writing asynchronous code. For example, inside the EventMachine environment you need to be able to issue an asynchronous call, suspend a block of code, and resume it when you receive the response.

This ends up looking like this:

async_call(argument1, argument2) do |response_1|
  if (response_1.ok?)
    async_call(argument3, argument4) do |response_2|
      if (response_2.ok?)
        async_call(argument5, argument6) do |response_3|
          if (response_3.ok?)
            do_something(response_1, response_2, response_3)
          else
            panic_and_fail!
          end
        end
      else
        panic_and_fail!
      end
    end
  else
    panic_and_fail!
  end
end

This sort of nested, nested and re-nested call structure is loosely termed "callback hell" as it gets very difficult to manage once your logic becomes non-trivial. One way to flatten this structure is to employ Fibers. A properly Fiber-ized equivalent is:

begin
  response_1 = fiber_call(argument1, argument2)
  response_2 = fiber_call(argument3, argument4)
  response_3 = fiber_call(argument5, argument6)

  do_something(response_1, response_2, response_3)

rescue NotOkay
  panic_and_fail!
end

Fibers can take advantage of exceptions, where callback-type code cannot. Exceptions, when used effectively, can massively simplify a block of code, as you can see here. Instead of testing for ok? on each response, it's expected that the call will throw an exception of type NotOkay instead.

Callbacks cannot reliably throw exceptions since the initiator of the call has already fallen out of scope when the callback occurs. This is a fundamental limitation of asynchronous programming with callbacks. Fiber driven code maintains a proper call stack, it's merely suspended and resumed as-is, so exceptions properly cascade through the caller.

I've found Fibers to be both simple to understand and very difficult to apply correctly. Most of the time you won't have to use them directly, you'll be using a library that employs them instead. Writing "Fiber-aware" code is not unlike writing "Thread-safe" code. It can be tricky to get right.

like image 167
tadman Avatar answered Sep 18 '22 21:09

tadman