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

Modelling Concurrency In Ruby

Shashank
October 11, 2016

Modelling Concurrency In Ruby

Presentation given at KCRUG

Shashank

October 11, 2016
Tweet

More Decks by Shashank

Other Decks in Programming

Transcript

  1. Concurrency != Parallelism Concurrency: Programming as composition of independently executing

    “instruction sets”. Parallelism: Programming as simultaneous execution of (possibly related) instruction sets. See Rob Pike: https://talks.golang.org/2012/waza.slide#1
  2. Parallel != Distributed Parallel: Same operating environment, no fault tolerance,

    global clock Distributed: Different operating environments, must consider fault tolerance, no global clock
  3. Ruby Process Heavy weight - no sharing between processes Parallel

    / Multicore execution possible Spawned Process can be any other executable (not necessarily Ruby based) OS Dependent functionality (e.g. fork is only on POSIX based OS, not Windows) Skipping code examples for Process in this presentation!
  4. Ruby Threads Native threads since Ruby 1.9.x Preemptive Scheduling Shared

    memory between threads (and source of many problems) MRI Ruby has Global Interpreter Lock (GIL) • Does NOT prevent concurrent execution: when a thread is blocking on IO, it releases the GIL so another thread can execute Ruby code.  • DOES prevent parallel execution of threads: multiple threads will NOT run simultaneously on multiple cores
  5. Ruby Threads require 'open-uri' urls = ["http://www.kcruby.org", "http://www.example.com", "http://www.google.com"] urls.map

    do |url| Thread.new do open(url){|f| p [f.base_uri.to_s, f.charset]} end end.map(&:join) #=>["http://www.example.com", "iso-8859-1"] #=>["http://www.google.com", "iso-8859-1"] #=>["http://www.kcruby.org", "utf-8"]
  6. Ruby Fibers Since Ruby 1.9.x (used to implement Enumerators) Cooperative

    Scheduling (programmer’s responsibility) Shared memory between Fibers Affected by GIL Light weight threads (you can create 100 K fibers but not as many threads)
  7. Ruby Fiber require 'fiber' fiber1 = Fiber.new do puts "In

    Fiber 1" Fiber.yield end fiber2 = Fiber.new do puts "In Fiber 2" fiber1.transfer puts "Never see this message" end fiber2.resume #=> In Fiber 2 #=> In Fiber 1
  8. Guild (new proposal in Ruby 3!) Koichi: https://www.youtube.com/watch?v=WIrYh14H9kA Thread +

    Fiber: http://olivierlacan.com/posts/concurrency-in-ruby-3-with-guilds/
  9. Higher Level Abstractions • Channels • Futures • Actors •

    Event Machines • Message Queues • Tuple Spaces • Agents
  10. Channels Channels are "pipes" through which values can be sent.

    When shared between concurrent Ruby blocks (a.k.a. goroutines) they provide a communication mechanism for coordinating asynchronous actions. They are thread safe and naturally concurrent.
  11. Channel Example require 'concurrent-edge' chan = Concurrent::Channel.new Concurrent::Channel.go do chan.put

    'ping' end msg = messages.take puts msg # Ordering of .put and .take is important Concurrent::Channel.go do puts chan.take end chan.put 'pong' puts "-- done --"
  12. Futures A future represents a promise to complete an action

    at some time in the future. The action (promise.value) is atomic and permanent. The idea behind a future is to send an operation for asynchronous completion, do other stuff, then return and retrieve the result of the async operation at a later time. Futures run on the global thread pool.
  13. Future Example require 'concurrent' require 'thread' # for Queue require

    'open-uri' # for open(uri) class Ticker def get_year_end_closing(symbol, year) uri = "http://ichart.finance.yahoo.com/table.csv?s=#{symbol}&a=11&b=01&c=#{year}&d=11&e=31&f=#{year }&g=m" data = open(uri) {|f| f.collect{|line| line.strip } } data[1].split(',')[4].to_f end end
  14. Future Example continued # Future price = Concurrent::Future.execute{ Ticker.new.get_year_end_closing('TWTR', 2013)

    } price.state #=> :pending price.pending? #=> true price.value(0) #=> nil (does not block) sleep(1) # do other stuff price.value #=> 63.65 (after blocking if necessary) price.state #=> :fulfilled price.fulfilled? #=> true price.value #=> 63.65
  15. Actor Like a thread, it runs concurrently with other Actors.

    Like a fiber, it is non preemptive. Unlike thread or fiber there is no shared memory. Message passing between two actors. It has a mailbox and a routine named “receive” to check its mailbox for new messages.
  16. Actor Example require 'concurrent-edge' class Counter < Concurrent::Actor::Context def initialize(initial_value)

    @count = initial_value end # override on_message to define actor's behaviour def on_message(message) @count += message if Integer === message end end
  17. Actor Example (continued) # Create new actor naming the instance

    'first'. # Return value is a reference to the actor, the actual actor is never returned. counter = Counter.spawn(:first, 5) # Tell a message and forget returning self. counter.tell(1) counter << 1 # Send a messages asking for a result. p counter.ask(0).value
  18. Where else? Channel Based Concurrency: Go, Occam Futures/Promises: Scheme, Clojure

    (diff. between Future and Promise) Actor Based Concurrency: Erlang, Elixir