Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby acting up: A look at how Celluloid implements the actor model for concurrency in Ruby.

Ruby acting up: A look at how Celluloid implements the actor model for concurrency in Ruby.

Arjan van der Gaag

November 11, 2015
Tweet

More Decks by Arjan van der Gaag

Other Decks in Programming

Transcript

  1. Ruby acting up
    A look at how Celluloid implements the actor

    model for concurrency in Ruby.

    View Slide

  2. encapsulated

    state
    message message

    View Slide

  3. Actor
    /ˈaktə/
    noun
    1. a participant in an action or process.

    View Slide

  4. Actor
    /ˈaktə/
    noun
    1. a participant in an action or process.
    2. a process with an inbox for receiving messages.

    View Slide

  5. Thread basics
    Thread.new do
    loop do
    # do something interesting
    end
    end

    View Slide

  6. Concurrency & Parallelism

    View Slide

  7. Thread basics
    Thread.new do
    loop do
    # do something interesting
    end
    end

    View Slide

  8. Starting and stopping
    @running = true
    Thread.new do
    while @running do
    # do something interesting
    end
    end

    View Slide

  9. Queues
    require 'thread'
    @running = true
    @queue = Queue.new
    Thread.new do
    while @running do
    message = @queue.pop
    # do something interesting with message
    end
    end

    View Slide

  10. Actor object
    class Actor
    def initialize
    @running = true
    @queue = Queue.new
    @thread = Thread.new do
    while @running do
    process_message(
    @queue.pop
    )
    end
    end
    end
    def stop
    @running = false
    end
    private
    def process_message(msg)
    # do stuff here
    end
    end

    View Slide

  11. Thread safety
    class Actor
    def initialize
    @counter = 0
    end
    def process_message(msg)
    @counter += 1
    end
    end

    View Slide

  12. Thread safety
    class Actor
    def initialize
    @counter = 0
    end
    def process_message(msg)
    @counter = (@counter + 1)
    end
    end

    View Slide

  13. Mutex
    def initialize
    @mutex = Mutex.new
    end
    def process_message(msg)
    @mutex.synchronize do
    # ...
    end
    end

    View Slide

  14. Queueing messages
    class Greeter
    def greet(name)
    puts "Hello, #{name}!"
    end
    end
    greeter = Greeter.new
    @queue << [:greet, "John"]
    greeter.send *@queue.pop

    View Slide

  15. Actor delegates to object
    class Actor
    def initialize(target)
    @target = target
    @thread = Thread.new do
    process_inbox
    end
    end
    def process_inbox
    while @running
    process_message *@queue.pop
    end
    end
    def process_message(func, *args)
    @mutex.synchronize do
    target.public_send(

    func,
    *args
    )
    end
    end
    end

    View Slide

  16. Actor send_async
    class Actor
    def send_async(*args)
    @queue << args
    end
    end
    class Greeter
    def greet(name)
    puts "Hello, #{name}!"
    end
    end
    greeter = Greeter.new
    actor = Actor.new(greeter)
    actor.send_async(
    :greet,
    'John'
    )

    View Slide

  17. Celluloid actor mixin
    module Celluloid
    def self.included(base)
    base.extend ClassMethods
    end
    module ClassMethods
    def new(*args)
    Actor.new(super)
    end
    end
    class Actor
    def method_missing(*args)
    target
    .public_send(*args)
    end
    end
    end

    View Slide

  18. Celluloid async proxy
    module Celluloid
    class Actor
    def initialize(target)
    @proxy = AsyncProxy.new(self)
    end
    def async
    @proxy
    end
    end
    class AsyncProxy
    def initialize(actor)
    @actor = actor
    end
    def method_missing(*args)
    @actor.send_async(*args)
    end
    end
    end

    View Slide

  19. Celluloid example
    class Pinger
    URL = "http://www.google.com/webmasters/tools/ping?sitemap=%s"
    include Celluloid
    def ping(sitemap_url)
    Net::HTTP.get(URL % sitemap_url)
    end
    end
    pinger = Pinger.new
    url = 'http://arjanvandergaag.nl/sitemap.xml'
    pinger.ping(url) # => wait... 200 OK"
    pinger.async.ping(url) # => nil

    View Slide

  20. Celluloid futures
    pinger.ping(url)
    # => "200 OK"
    future = pinger.future.ping(url)
    # => #
    future.value
    # => "200 OK"

    View Slide

  21. Ruby promises with Whenner
    pinger.ping.then do |on|
    on.done do |value|
    puts "Done with status #{value}"
    end
    on.fail do |value|
    puts "Something bad happened!"
    end
    end
    Whenner.when(pinger.ping, ponger.pong).done do |results|
    results.each do |status|
    puts status
    end
    end

    View Slide

  22. Actors are objects
    handling messages in a
    separate thread.

    View Slide

  23. Find out more
    • Celluloid
    • Celluloid::IO
    • Concurrent Programming with Celluloid (Tony Arcieri,
    MountainWest RubyConf 2012)
    • Sidekiq
    • RubyTapas
    • Whenner

    View Slide