Fibers, Threads and Continuations

The difference between them is not always obvious, so let’s have a quick recap.

Threads

Threads are a simple way to have multiple sections of code being ran at the same time. The purist will now complain and tell us about the GIL and green threads, but for the sake of this post let’s keep things simple.

threads = []

3.times do |i|
  threads << Thread.new(i) do |x|
    puts "Starting thread #{x}\n"
    sleep rand
    puts "Finishing thread #{x}\n"
  end
end

threads.each {|t| t.join }

This is the output:

Starting thread 0
Starting thread 1
Starting thread 2
Finishing thread 1
Finishing thread 2
Finishing thread 0

BTW: have you noticed the Thread.new(i) do |x|? Any arguments passed to the Thread constructor are passed to the block, and we do this to make sure we’re using a local variable (in the thread scope) rather than i, that belongs to the outer scope and that could change without us noticing!

BTW#2: what’s the join for? It’s there to make sure the thread is completed before terminating the program, otherwise the threads will be killed without waiting for them.

Shared variables

Local variables are local to the thread; if you need a shared variable across threads you can use the current thread object as a hash:

Thread.current[:shared_variable] = 123

Thread methods

Fibers

Fibers have been introduced in Ruby 1.9. They can be considered a lightweight thread. The main difference is that with threads the runtime takes care of running them and switching them (although you can control the thread scheduler with stop/run etc), while with fibers we have to manually start and stop their execution (you have no preemption). The documentation is very clear:

Fibers are primitives for implementing light weight cooperative concurrency in Ruby. Basically they are a means of creating code blocks that can be paused and resumed, much like threads. The main difference is that they are never preempted and that the scheduling must be done by the programmer and not the VM.

When a fiber is created it will not run automatically. Rather it must be be explicitly asked to run using the Fiber#resume method. The code running inside the fiber can give up control by calling Fiber.yield in which case it yields control back to caller (the caller of the Fiber#resume).

So the two main methods you use on a fiber are resume and yield. Here is a simple example:

f = Fiber.new do
  puts 'before yield'
  Fiber.yield
  puts 'after yield'
end

f.resume
puts 'in between'
f.resume

The output is:

before yield
in between
after yield

So the fiber is first manually started, then it returns the control to the caller, then the caller makes it continue.

Continuations

Continuations are a concept that comes from the functional programming world (but they exist in C, too, with setjmp and longjmp). They’re the functional equivalent of the dreaded GOTO statement. In ruby they are an object that stores a return address and an execution context (you can think to it as a binding). They can be used to save the state of the running program and resume it afterwards.

let’s make a concrete example, since all those I’ve found on the web weren’t clean enough to me:

require 'continuation'   # required!
c = callcc {|cont| cont} # the block argument is required, too

The callcc method returns a Continuation object. The code block will be executed right away:

require 'continuation'

puts 1

callcc do |cont|
  puts 2
  puts 3
end

puts 4

Output:

1
2
3
4

When you call the call method on the Continuation object, then the code execution will resume at the end of the block! If you make such a call inside the block itself, then the block execution stops, just like it was finished.

require 'continuation'

puts 1

callcc do |cont|
  puts 2
  cont.call  # This jumps out of the block!
  puts 3     # This is skipped
end

puts 4

Output:

1
2
4

So far this seems still pretty lame. It becomes interesting when we start passing continuation objects around: this allows us to GOTO through different parts of the app:

require 'continuation'

def ping
  puts "Let's start!"
  callcc {|cc| $goto_label = cc }
  puts "ping"
  pong
end

def pong
  puts "pong"
  $goto_label.call
end

ping

Do you remember the old BASIC loop?

10 PRINT "hello"
20 GOTO 10

Well here it is in Ruby:

require 'continuation'

callcc {|cont| $cont = cont }
print "hello"
$cont.call