Search code examples
rubyfibers

Simple parallelism with Fibers?


I'm trying to validate a basic yield/resume pattern with two Fibers. However, the yield/resume mechanism doesn't seem to work. I have tried a few variations but can't find my mistake.

Expected sequence of events:

  1. kick off threads (time 0)
  2. wait until first returns (time +2sec)
  3. wait until 2nd returns (time +2sec)
  4. done (time > +3sec)

Try #1

#!/usr/bin/env ruby

require 'fiber'

f1 = Fiber.new do
    puts "Fiber1 starting @ #{Time.new}."
    fib1 = Fiber.current
    Fiber.yield
    sleep 2
    puts "Fiber1 done @ #{Time.new}."
    fib1.resume(1)
end
f2 = Fiber.new do
    puts "Fiber2 starting @ #{Time.new}."
    fib2 = Fiber.current
    Fiber.yield
    sleep 2
    puts "Fiber2 done @ #{Time.new}."
    fib2.resume(2)
end

puts "Waiting @ #{Time.new}."
r1 = f1.resume
puts "f1 back @ #{Time.new} - #{r1}."
r2 = f2.resume
puts "f2 back @ #{Time.new} - #{r2}."

sleep 1
puts "Done @ #{Time.now}."

This results in:

Waiting @ 2016-06-01 06:15:52 -0700.
Fiber1 starting @ 2016-06-01 06:15:52 -0700.
f1 back @ 2016-06-01 06:15:52 -0700 - .
Fiber2 starting @ 2016-06-01 06:15:52 -0700.
f2 back @ 2016-06-01 06:15:52 -0700 - .
Done @ 2016-06-01 06:15:53 -0700.

Adding a second resume results in a FiberError.

Try #2

#!/usr/bin/env ruby

require 'fiber'

f1 = Fiber.new do
    puts "Fiber1 starting @ #{Time.new}."
    Fiber.yield
    sleep 2
    puts "Fiber1 done @ #{Time.new}."
    1
end
f2 = Fiber.new do
    puts "Fiber2 starting @ #{Time.new}."
    Fiber.yield
    sleep 2
    puts "Fiber2 done @ #{Time.new}."
    2
end

puts "Waiting @ #{Time.new}."
r1 = f1.resume
puts "f1 back @ #{Time.new} - #{r1}."
r2 = f2.resume
puts "f2 back @ #{Time.new} - #{r2}."

sleep 1
puts "Done @ #{Time.now}."

This results in:

Waiting @ 2016-06-01 10:53:17 -0700.
Fiber1 starting @ 2016-06-01 10:53:17 -0700.
f1 back @ 2016-06-01 10:53:17 -0700 - .
Fiber2 starting @ 2016-06-01 10:53:17 -0700.
f2 back @ 2016-06-01 10:53:17 -0700 - .
Done @ 2016-06-01 10:53:18 -0700.

In both cases, the start/end time is the same and result not returned.


Solution

  • Fibers by themselves will not let you achieve parallelism, at least not without using some sort of callback mechanism such as eventmachine framework.

    What you wrote is simply trying to interleave synchronous execution among code blocks. The reason you do not get expected sequence is because while you did simulate the kick-off, you never resumed the fibers after yeilding.

    You might find the following post useful, particularly the example at the end:
    http://schmurfy.github.io/2011/09/25/on_fibers_and_threads.html

    Another example showing fibers transferring control to each other:
    https://gist.github.com/aprescott/971008

    This should give you expected results:

    #!/usr/bin/env ruby
    
    require 'fiber'
    
    f1 = Fiber.new do
        puts "Fiber1 starting @ #{Time.new}."
        Fiber.yield
        sleep 2
        puts "Fiber1 done @ #{Time.new}."
        1
    end
    f2 = Fiber.new do
        puts "Fiber2 starting @ #{Time.new}."
        Fiber.yield
        sleep 2
        puts "Fiber2 done @ #{Time.new}."
        2
    end
    
    puts "Waiting @ #{Time.new}."
    r1 = f1.resume
    puts "f1 back @ #{Time.new} - #{r1}."
    r2 = f2.resume
    puts "f2 back @ #{Time.new} - #{r2}."
    
    # Resume right after the yield in the fiber block and 
    # execute until it encounters another yield or the block ends.
    puts "Resuming f1"
    f1.resume 
    puts "Resuming f2"
    f2.resume
    
    sleep 1
    puts "Done @ #{Time.now}."
    

    Output:

    Waiting @ 2016-06-05 00:35:29 -0700.
    Fiber1 starting @ 2016-06-05 00:35:29 -0700.
    f1 back @ 2016-06-05 00:35:29 -0700 - .
    Fiber2 starting @ 2016-06-05 00:35:29 -0700.
    f2 back @ 2016-06-05 00:35:29 -0700 - .
    Resuming f1
    Fiber1 done @ 2016-06-05 00:35:31 -0700.
    Resuming f2
    Fiber2 done @ 2016-06-05 00:35:33 -0700.
    Done @ 2016-06-05 00:35:34 -0700.