Ruby Concurrency and EventMachine

14,311 views

Published on

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

Published in: Technology
1 Comment
18 Likes
Statistics
Notes
No Downloads
Views
Total views
14,311
On SlideShare
0
From Embeds
0
Number of Embeds
1,804
Actions
Shares
0
Downloads
81
Comments
1
Likes
18
Embeds 0
No embeds

No notes for slide
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Pros: Lots of threads; Cheap to create, execute & cleanup\nCons: Kernel doesn’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‘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
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • 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/

    ×