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

Minx - Communicating Sequential Processes in Ruby

Minx - Communicating Sequential Processes in Ruby

A Ruby implementation of the CSP language defined by Tony Hoare.

Avatar for Daniel Schierbeck

Daniel Schierbeck

October 10, 2013
Tweet

More Decks by Daniel Schierbeck

Other Decks in Technology

Transcript

  1. Minx Topics Covered • Concurrency models • threads • call/cc

    • fibers • My awesome concurrency library, Minx!
  2. Minx What is concurrency? • Computations happen simultaneously • Not

    necessarily at the same exact time • Allows us to program non-sequentially
  3. Minx What we’re used to • Green threads • Native

    threads • Reactor pattern (event driven code)
  4. Minx Green threads • Allows us to keep doing stuff

    while waiting on I/O • Doesn’t use more than one core, so no performance benefit besides the I/O stuff • Shared state is a problem for some uses
  5. Minx Native threads • Utilizes all your cores • Shared

    state is a bitch • Synchronization around shared state is really hard
  6. Example: shared state 1 class Account 2 def transfer(recipient, amount)

    3 lock(self) do 4 lock(recipient) do 5 withdraw(amount) 6 recipient.deposit(amount) 7 end 8 end 9 end 10 end
  7. Minx A formal language • Consists of two primitives: •

    Processes are independent pieces of behavior • Events represent communication or interaction
  8. Minx Events drive change • Processes react to changes: a

    -> P describes a process that waits for a and then behaves like P • Processes can wait for different events simultaneously: (a -> P ⬛ b -> Q) • Lots of interesting stuff, but really geeky.
  9. Minx An adaptation of CSP • Processes are still the

    central primitives used to describe behavior • Channels are used for communications and synchronization between processes • Channels are basically parameterized events
  10. Communicating between processes chan = Minx.channel a = Minx.spawn {

    chan << “hello” } b = Minx.spawn { puts “received: “ + chan.read } Minx.join(a, b) # => “received: hello”
  11. Reading from multiple channels msg = Minx.select(chan1, chan2) Order of

    channels does not matter, so deadlocks are much easier to avoid.
  12. Minx Composite processes • Use several processes behind the scene

    • Still act sequentially to the outside world
  13. Minx Solving problems with Minx • Protect critical data with

    channels and processes • Use channels as synchronization primitives
  14. The Account example 1 class Account 2 attr_reader :balance 3

    4 def initialize(options = {}) 5 @deposits = Minx.channel 6 7 @balance = options.fetch(:balance, 0.0) 8 9 Minx.spawn do 10 @deposits.each {|amount| @balance += amount } 11 end 12 end 13 end
  15. 1 class Account 2 def deposit(amount) 3 @deposits << amount

    4 end 5 6 def withdraw(amount) 7 @deposits << -amount 8 end 9 10 def transfer(recipient, amount) 11 Minx.join( 12 Minx.spawn { withdraw(amount) }, 13 Minx.spawn { recipient.deposit(amount) }) 14 end 15 end
  16. 1 acc1 = Account.new(:balance => 100.0) 2 acc2 = Account.new(:balance

    => 50.0) 3 4 acc1.transfer(acc2, 60.0) 5 6 acc1.balance #=> 40.0 7 acc2.balance #=> 110.0
  17. Implementing GOTO 1 require 'continuation' 2 3 names = %w[John

    James Max David] 4 5 callcc {|cc| $cc = cc } 6 7 name = names.shift 8 puts name 9 10 $cc.call unless name == "Max" 11 12 # Outputs: 13 # 14 # John 15 # James 16 # Max
  18. Simple exceptions 1 require 'continuation' 2 3 $cc_stack = []

    4 5 def try(&block) 6 callcc do |cc| 7 begin 8 $cc_stack.push(cc) 9 block.call 10 ensure 11 $cc_stack.pop 12 end 13 end 14 end 15 16 def error! 17 $cc_stack.pop.call 18 end
  19. Simple exceptions 1 try do 2 puts "a" 3 error!

    4 puts "b" 5 end 6 7 # Outputs: 8 # 9 # a Multiple exits from method calls
  20. Minx Issues with call/cc • Very difficult to manage continuations

    • Poor exception handling • Stuff would easily get lost
  21. Minx Fibers • Provides a simple API for defining “fibers”

    of execution • Like threads, but non-preemptive • Allows granular control of execution
  22. Python-like generators 1 require 'fiber' 2 3 fiber = Fiber.new

    do 4 Fiber.yield :uno 5 Fiber.yield :dos 6 :tres 7 end 8 9 fiber.resume #=> :uno 10 fiber.resume #=> :dos 11 fiber.resume #=> :tres 12 fiber.resume #=> FiberError: dead fiber called Multiple method exits!
  23. Ping-Pong! 1 ping = Fiber.new do |pong| 2 while true

    3 puts "ping!" 4 sleep 0.2 5 pong.transfer 6 end 7 end 8 9 pong = Fiber.new do 10 while true 11 puts "pong!" 12 sleep 0.2 13 ping.transfer 14 end 15 end 16 17 ping.resume(pong) Never overflows the stack!
  24. Non-blocking I/O 1 def read(io) 2 Fiber.yield until io.ready? 3

    io.read 4 end No more callbacks! 1 data = {} 2 fibers = files.map {|f| Fiber.new { data[f] = read(file) } } 3 4 until fibers.none? {|fiber| fiber.alive? } 5 fibers.each {|fiber| fiber.resume if fiber.alive? } 6 end
  25. Minx Control Flow • Fibers can pass on execution to

    each other • #resume is best used for caller/callee scenarios • #transfer is used for peer-to-peer communication
  26. Minx #resume 1 fib1 = Fiber.new do 2 puts "foo"

    3 Fiber.yield 4 puts "bar" 5 end 6 7 fib2 = Fiber.new do 8 fib1.resume 9 puts "baz" 10 end 11 12 fib2.resume 13 # foo 14 # baz
  27. Minx #transfer 1 fib1 = Fiber.new do 2 puts "foo"

    3 Fiber.yield 4 puts "bar" 5 end 6 7 fib2 = Fiber.new do 8 fib1.transfer 9 puts "baz" 10 end 11 12 fib2.resume 13 # foo
  28. Minx Passing Messages 1 def receive 2 message = Fiber.yield

    3 end 4 5 def send(recipient, message) 6 recipient.transfer(message) 7 end 8 9 fib1 = Fiber.new { puts receive } 10 fib2 = Fiber.new { send(fib1, "hello!") } 11 12 fib1.resume 13 fib2.resume