29. Fibers
• It’s a coroutine, dammit!
• “... [component to] generalize
subroutines to allow multiple entry
points for suspending and resuming
execution at certain locations.”
30. file_iterator = Fiber.new do
file = File.open('stuff.csv', 'r')
while line = file.gets
Fiber.yield line
end
file.close
end
3.times{ file_iterator.resume }
# => line 1
# => line 2
# => line 3
31. Fibers
• Cooperative Scheduling
• Very lightweight
• Maintains state
• Great for: cooperative tasks, iterators,
infinite lists and pipes
32. def interpret_csv( csv_source )
Fiber.new do
while csv_source.alive?
str = csv_source.resume
Fiber.yield str.split(',').map(&:strip)
end
end
end
def file_iterator(file_name)
Fiber.new do
file = File.open(file_name, 'r')
while line = file.gets
Fiber.yield line
end
file.close
end
end
interpret_csv( file_iterator('stuff.csv') ).resume
# => [...]
38. require 'eventmachine'
class EchoServer < EM::Connection
def post_init
puts "New connecting"
end
def unbind
puts "Connection closed"
end
def receive_data(data) # unbuffered!!
puts "<< #{data}"
send_data ">> #{data}"
end
end
EM.run do
EM.start_server('127.0.0.1', 9000, EchoServer)
puts "Started server at 127.0.0.1:9000"
end # Runs till EM.stop called
39. # $ telnet localhost 9000
require 'eventmachine' # Hello
# >> Hello
class EchoServer < EM::Connection # Bye
def post_init # >> Bye
puts "New connecting"
end
def unbind
puts "Connection closed"
end
def receive_data(data) # unbuffered!!
puts "<< #{data}"
send_data ">> #{data}"
end
end
EM.run do
EM.start_server('127.0.0.1', 9000, EchoServer)
puts "Started server at 127.0.0.1:9000"
end # Runs till EM.stop called
41. EM.run do
Defer
operation = Proc.new do
puts 'MapMap!!'
sleep 3
puts 'Done collecting data'
[1, 1, 2, 3, 5, 8, 13]
end
callback = Proc.new do |arr|
puts 'Reducing...'
sleep 1
puts 'Reduced'
puts arr.inject(:+)
EM.stop
end
EM.defer(operation, callback)
end
42. EM.run do
Defer #
#
#
MapMap!!
Done collecting data
Reducing...
# Reduced
operation = Proc.new do
# 33
puts 'MapMap!!'
sleep 3
puts 'Done collecting data'
[1, 1, 2, 3, 5, 8, 13]
end
callback = Proc.new do |arr|
puts 'Reducing...'
sleep 1
puts 'Reduced'
puts arr.inject(:+)
EM.stop
end
EM.defer(operation, callback)
end
43. EM.run do
Queue
queue = EM::Queue.new
EM.defer do
sleep 2; queue.push 'Mail 1'
sleep 3; queue.push 'Mail 2'
sleep 4; queue.push 'Mail 3'
end
mail_sender = Proc.new do |mail|
puts "Sending #{mail}"
EM.next_tick{ queue.pop(&mail_sender)}
end
queue.pop(&mail_sender)
end
44. Channel
EM.run do
channel = EM::Channel.new
EM.defer do
channel.subscribe do |msg|
puts "Received #{msg}"
end
end
EM.add_periodic_timer(1) do
channel << Time.now
end
end
45. class Mailer
include EM::Deferrable Deferrable
def initialize
callback do
sleep 1
puts 'Updated statistics!'
end
errback{ puts 'retrying mail'}
end
def send
rand >= 0.5 ? succeed : fail
end
end
EM.run do
5.times do
mailer = Mailer.new
EM.add_timer(rand * 5){ mailer.send}
end
end
46. class Mailer
include EM::Deferrable Deferrable
def initialize # Updating statistics!
callback do # Updating statistics!
sleep 1 # retrying mail
puts 'Updated statistics!'
end
errback{ puts 'retrying mail'}
end
def send
rand >= 0.5 ? succeed : fail
end
end
EM.run do
5.times do
mailer = Mailer.new
EM.add_timer(rand * 5){ mailer.send}
end
end
47. class Mailer
Stacked callbacks
include EM::Deferrable
EM.run do
def add_mailing(val)
m = Mailer.new
callback{
m.add_mailing(1)
sleep 1;
m.add_mailing(2)
puts "Sent #{val}"
m.connection_open!
}
end
EM.add_timer(1) do
m.connection_lost!
def connection_open!
EM.add_timer(2) do
puts 'Open connection'
m.add_mailing(3)
succeed
m.add_mailing(4)
end
m.connection_open!
end
def connection_lost!
end
puts 'Lost connection'
end
set_deferred_status nil
end
end
48. class Mailer
Stacked callbacks
include EM::Deferrable
EM.run do
def add_mailing(val)
m = Mailer.new
callback{ # Open connection
m.add_mailing(1)
sleep 1;
m.add_mailing(2) # Sent 1
puts "Sent #{val}"
m.connection_open! # Sent 2
}
end # Lost connection
EM.add_timer(1) do
# Open connection
m.connection_lost!
def connection_open! # Sent 3
EM.add_timer(2) do
puts 'Open connection'
m.add_mailing(3) # Sent 4
succeed
m.add_mailing(4)
end
m.connection_open!
end
def connection_lost!
end
puts 'Lost connection'
end
set_deferred_status nil
end
end
49. Gotchas
• Synchronous code will slow it down
• Use/Write libraries for EM
• Everything in the event loop must be async!
50. Summary
• It’s a blocking world!
• Alternative concurrency implementations
• Start playing with EM
51. Worth checking out
• EM-Synchrony:
https://github.com/igrigorik/em-synchrony
• Goliath:
https://github.com/postrank-labs/goliath
Pros: Lots of threads; Cheap to create, execute & cleanup\nCons: Kernel doesn&#x2019;t know about threads; Blocking\ne.g. new green thread for every http request that comes in... \n
Pros: Non blocking; Multi core systems; Shared memory\nCons: Expensive to create; complex context switching; far fewer threads\n
Pros: Best of both worlds: Multiple CPUS; Not all threads blocked by system calls; Cheap creation, execution & cleanup\nCons: Green threads blocking on IO can block other Green threads in kernel thread; Hard; Kernel and User scheduler need to work together\n
Resource utilization\nAsync IO\n
Resource utilization\nAsync IO\n
Resource utilization\nAsync IO\n
\n
Ruby has a legacy of being thread unsafe (e.g. rails only became thread safe 2.2&#x2018;ish)\n1.9 Ruby code does not execute on more than one thread concurrently!\n
\n
\n
\n
\n
\n
\n
\n
\n
Fast and cheap to setup\n
\n
\n
\n
\n
Inverted flow control (callback hell)\n... which limit concurrency\n\n
Toolkit for creating evented apps\n
EM interchangeable with EventMachine\n
next_tick -> run code at the next opportunity (always run in main thread)\ndefer -> defer work to run on a thread (green) - 20 by default\nQueue -> data\nChannel -> comms\n