Ruby Concurrency and EventMachine

14,813 views

Published on

RubyOnBeer presentation with a brief overview of concurrency in ruby and some coverage of EventMachine basics

Published in: Technology

Ruby Concurrency and EventMachine

  1. 1. Ruby Concurrency...and the mysterious case of the Reactor Pattern Christopher Spring
  2. 2. or...
  3. 3. WTF the EventMachineand why should I care?
  4. 4. WTF the EventMachineand why should I care?
  5. 5. WTF the EventMachineand why should I care?
  6. 6. Resource utilization • Lots of IO (90/10) • Disk • Network • system() • Lots of coreshttp://www.mikeperham.com/2010/01/27/scalable-ruby-processing-with-eventmachine/
  7. 7. Ruby concurrency basics • Threads • Fibers • Processes
  8. 8. Threading Models 1: N 1 :1 M :N
  9. 9. Threading Models 1: NKernel Threads 1 :1 User Threads M :N
  10. 10. Threading Models 1: N 1 :1 M :NKernel Threads User Threads
  11. 11. Threading Models 1: N • Green Threads • Ruby 1.8 1 :1 • Pros/Cons M :NKernel Threads User Threads
  12. 12. Threading Models 1: N 1 :1 M :NKernel Threads User Threads
  13. 13. Threading Models 1: N • Native Threads • Ruby 1.9 / jRuby 1 :1 • Pros/Cons M :NKernel Threads User Threads
  14. 14. Threading Models 1: N 1 :1 ? M :NKernel Threads User Threads
  15. 15. Threads
  16. 16. Threads• Shared state and memory space
  17. 17. Threads• Shared state and memory space• Relatively light weight
  18. 18. Threads• Shared state and memory space• Relatively light weight• Preemptive scheduling
  19. 19. Ruby has baggage: GIL http://www.igvita.com/2008/11/13/concurrency-is-a-myth-in-ruby/
  20. 20. Threads... suck
  21. 21. Threads... suck• Race conditions
  22. 22. Threads... suck• Race conditions• Deadlocks
  23. 23. Threads... suck• Race conditions• Deadlocks• Hard to debug
  24. 24. Threads... suck• Race conditions• Deadlocks• Hard to debug• GIL
  25. 25. Threads... suck• Race conditions• Deadlocks• Hard to debug• GIL
  26. 26. Fibers
  27. 27. Fibers• It’s a coroutine, dammit!
  28. 28. Fibers• It’s a coroutine, dammit! • “... [component to] generalize subroutines to allow multiple entry points for suspending and resuming execution at certain locations.”
  29. 29. file_iterator = Fiber.new do file = File.open(stuff.csv, r) while line = file.gets Fiber.yield line end file.closeend3.times{ file_iterator.resume }# => line 1# => line 2# => line 3
  30. 30. Fibers• Cooperative Scheduling• Very lightweight• Maintains state• Great for: cooperative tasks, iterators, infinite lists and pipes
  31. 31. def interpret_csv( csv_source ) Fiber.new do while csv_source.alive? str = csv_source.resume Fiber.yield str.split(,).map(&:strip) end endenddef file_iterator(file_name) Fiber.new do file = File.open(file_name, r) while line = file.gets Fiber.yield line end file.close endendinterpret_csv( file_iterator(stuff.csv) ).resume# => [...]
  32. 32. Reactor Pattern
  33. 33. Client Client Client Client IO StreamEvent Handler A Event DispatcherEvent Handler B DemultiplexerEvent Handler CEvent Handler D
  34. 34. Benefits• Non blocking IO• Coarse grain concurrency• No threads!• Single Process
  35. 35. Limitations• Hard to debug• Dispatch to event handlers is synchronous• Demultiplexer polling limits
  36. 36. EventMachine
  37. 37. require eventmachineclass 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}" endendEM.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
  38. 38. # $ telnet localhost 9000require eventmachine # Hello # >> Helloclass 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}" endendEM.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. 39. Primitives• run loop• next_tick; add_timer; add_periodic_timer• EM.defer• EM::Queue• EM::Channel
  40. 40. 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
  41. 41. 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
  42. 42. 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
  43. 43. ChannelEM.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 endend
  44. 44. 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 endendEM.run do 5.times do mailer = Mailer.new EM.add_timer(rand * 5){ mailer.send} endend
  45. 45. 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 endendEM.run do 5.times do mailer = Mailer.new EM.add_timer(rand * 5){ mailer.send} endend
  46. 46. 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 endend
  47. 47. 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 endend
  48. 48. Gotchas• Synchronous code will slow it down • Use/Write libraries for EM• Everything in the event loop must be async!
  49. 49. Summary• It’s a blocking world!• Alternative concurrency implementations• Start playing with EM
  50. 50. Worth checking out• EM-Synchrony: https://github.com/igrigorik/em-synchrony• Goliath: https://github.com/postrank-labs/goliath
  51. 51. Baie Dankie!
  52. 52. Questions?
  53. 53. Links• http://www.mikeperham.com• http://www.igvita.com (!!)• http://rubyeventmachine.com/

×