Loading…

Flash Player 9 (or above) is needed to view presentations.
We have detected that you do not have it on your computer. To install it, go here.

Like this presentation? Why not share!

Concurrency: Rubies, Plural

on

  • 2,093 views

A mad recap of concurrency mechanism in Ruby, and how to steal the best idioms from other languages

A mad recap of concurrency mechanism in Ruby, and how to steal the best idioms from other languages

Statistics

Views

Total Views
2,093
Views on SlideShare
2,092
Embed Views
1

Actions

Likes
2
Downloads
41
Comments
0

1 Embed 1

https://www.linkedin.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

CC Attribution-NonCommercial-NoDerivs LicenseCC Attribution-NonCommercial-NoDerivs LicenseCC Attribution-NonCommercial-NoDerivs License

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Concurrency: Rubies, Plural Concurrency: Rubies, Plural Presentation Transcript

  • Concurrency: Rubies, Plural Elise Huard & Eleanor McHugh RubyConf 2010 Friday 12 November 2010
  • manifesto concurrency matters Friday 12 November 2010
  • multicore it’s a revolution in mainstream computing and you want to exploit it in Ruby Friday 12 November 2010
  • MULTIPROCESSOR/MULTICORE Friday 12 November 2010
  • NETWORK ON CHIP (50..96..100 CORES) Friday 12 November 2010
  • diminishing returns • communication takes finite time • so doubling processors never doubles a system’s realworld performance • realtime capacity ~70% theoretical capacity • or less!!! • independence = performance Friday 12 November 2010
  • “... for the first time in history, no one is building a much faster sequential processor. If you want your programs to run significantly faster (...) you’re going to have to parallelize your program.” Hennessy and Patterson “Computer Architectures” (4th edition, 2007) Friday 12 November 2010
  • concurrency why it really matters Friday 12 November 2010
  • an aide to good design • decouples independent tasks • encourages data to flow efficiently • supports parallel execution • enhances scalability • improves program comprehensibility Friday 12 November 2010
  • Friday 12 November 2010
  • Friday 12 November 2010
  • Friday 12 November 2010
  • finding green pastures adopting concurrency idioms from other languages Friday 12 November 2010
  • victims of choice Erlang Actors Go Concurrent Sequential Processes Clojure Software Transactional Memory Icon Coexpressions Friday 12 November 2010
  • coroutines synchronising via transfer of control Friday 12 November 2010
  • icon • a procedural language • with a single thread of execution • generators are decoupled coexpressions • and goal-directed evaluation • creates flexible flow-of-control Friday 12 November 2010
  • icon coexpressions procedure main() procedure cubes() n := create(seq(1)10) odds := 1 s := create(squares()) sum := 1 c := create(cubes()) i := create(seq(2)) while write(@n, “t”, @s, “t”, @c) repeat { end suspend sum sum +:= 1 + (odds * 6) procedure seq(start) odds +:= @i repeat { } suspend start end start += 1 } end 1 1 1 procedure squares() 2 4 8 odds := 3 3 9 27 sum := 1 4 16 64 repeat { 5 25 125 suspend sum 6 36 216 sum +:= odds odds +:= 2 7 49 343 } 8 64 512 end 9 81 729 10 100 1000 Friday 12 November 2010
  • ruby fibers • coexpressions • bound to a single thread • scheduled cooperatively • the basis for enumerators • library support for full coroutines Friday 12 November 2010
  • ruby coexpressions def seq start = 1 c = Fiber.new do Fiber.new do sum, odds, i = 1, 1, seq(2) loop do loop do Fiber.yield start Fiber.yield sum start += 1 sum += 1 + (odds * 6) end odds += i.resume end end end end n = seq() 10.times do puts “#{n.resume}t#{s.resume}t#{c.resume}” s = Fiber.new do end sum, odds = 1, 3 1 1 1 loop do Fiber.yield sum 2 4 8 sum += odds 3 9 27 odds += 2 4 16 64 end 5 25 125 end 6 36 216 7 49 343 8 64 512 9 81 729 10 100 1000 Friday 12 November 2010
  • ruby coroutines require 'fiber' def login Fiber.new do |server, name, password| puts "#{server.transfer(Fiber.current)} #{name}" puts "#{server.transfer name} #{password}" puts “login #{server.transfer password}” end end def authenticate Fiber.new do |client| name = client.transfer "name:" password = client.transfer "password:" if password == "ultrasecret" then client.transfer "succeeded" name: jane doe else client.transfer "failed" password: ultrasecret end login succeeded end end name: john doe login.transfer authenticate, "jane doe", "ultrasecret" password: notsosecret login.transfer authenticate, "john doe", "notsosecret" login failed Friday 12 November 2010
  • icon revisited procedure powers() global queue repeat { while e := get(queue) do procedure main() write(e, “t”, e^ 2, “t”, e^ 3) queue := [] e @&source process{ powers(), 1 to 5, seq(6, 1)5 } } end end procedure process(L) consumer := get(L) every producer := !L do while put(queue, @producer) do if *queue > 3 then @consumer 1 1 1 @consumer end 2 4 8 3 9 27 4 16 64 5 25 125 6 36 216 7 49 343 8 64 512 9 81 729 10 100 1000 Friday 12 November 2010
  • a ruby equivalent require 'fiber' low_seq = Fiber.new do 5.times { |i| Fiber.yield i + 1 } def process consumer, *fibers nil q = consumer.transfer(Fiber.current) end fibers.each do |fiber| while fiber.alive? high_seq = Fiber.new do q.push(fiber.resume) (6..10).each { |i| Fiber.yield i } q = consumer.transfer(q) if q.length end >3 end process powers, low_seq, high_seq end consumer.transfer q end 1 1 1 powers = Fiber.new do |caller| 2 4 8 loop do 3 9 27 caller.transfer([]).each do |e| 4 16 64 puts "#{e}t#{e ** 2}t#{e ** 3}" rescue nil 5 25 125 end 6 36 216 end end 7 49 343 8 64 512 9 81 729 10 100 1000 Friday 12 November 2010
  • processes the traditional approach to concurrency Friday 12 November 2010
  • Your program language VM OS (kernel processes, other processes) multicore - multiCPU Friday 12 November 2010
  • processes + threads memory space RAM memory space Process 1 Process 2 thread1 thread2 t1 t2 t3 scheduler (OS) CPU CPU Friday 12 November 2010
  • schedulers cooperative preemptive active task has full control scheduler controls task activity runs until it yields control switches tasks automatically tasks transfer control directly a task can still yield control within one thread several threads Friday 12 November 2010
  • 3 threads, 2 cores 1 1 2 2 3 1 3 3 2 1 core1 core2 core1 core2 Friday 12 November 2010
  • feature comparison process thread address space private shared kernel resources private + shared shared scheduling kernel varies communication IPC via kernel in process control children in process Friday 12 November 2010
  • process creation • unix spawns • windows cuts from whole cloth • ruby wraps this many ways • but we’re mostly interested in fork Friday 12 November 2010
  • fork • creates a new process • in the same state as its parent • but executing a different code path • forking is complicated by pthreads • and ruby’s default garbage collector Friday 12 November 2010
  • pipes • creates an I/O channel between processes • unnamed pipes join two related processes • posix named pipes • live in the file system • have user permissions • persist independently of processes Friday 12 November 2010
  • forking def execute &block child_input, parent_input = IO.pipe pid = fork do child_input.close result = block.call parent_input.write result.to_json parent_input.close end parent_input.close sorted = JSON.parse child_input.read child_input.close Process.waitpid pid return sorted end Friday 12 November 2010
  • context switching Operating System Benchmark Operation Time (ms) spawn new process fork() / exec() 6,000 clone current process fork() 1,000 Linux spawn new thread pthread_create 0,300 switch current process sched_yield() 0,019 switch current thread sched_yield() 0,019 spawn new process spawnl() 12,000 clone current process N/A --- Windows NT spawn new thread pthread_create() 0,900 switch current process Sleep(0) 0,010 switch current thread Sleep(0) 0,006 C Benchmarks by Gregory Travis on a P200 MMX http://cs.nmu.edu/~randy/Research/Papers/Scheduler/ Friday 12 November 2010
  • shared state hurts • non-determinism • atomicity • fairness/starvation • race conditions • locking • transactional memory Friday 12 November 2010
  • semaphores • exist independently of processes • provide blocking access • allowing processes to be synchronised • nodes in the file system • usable from Ruby with syscall Friday 12 November 2010
  • synchronising processes require ‘dl’ require ‘fcntl’ libc = DL::dlopen ‘libc.dylib’ open = libc[‘sem_open’, ‘ISII’] try_wait = libc[‘sem_trywait’, ‘II’] wait = libc[‘sem_wait’, ‘II’] post = libc[‘sem_post’, ‘II’] close = libc[‘sem_close’, ‘II’] process 1 process 2 s = open.call(“/tmp/s”, Fcntl::O_CREAT, 1911)[0] s = open.call(“/tmp/s”) wait.call s t = Time.now puts “locked at #{Time.now}” if try_wait.call(s)[0] == 0 then sleep 50 puts “locked at #{t}” puts “posted at #{Time.now}” else post.call s puts “busy at #{t}” close.call s wait.call s puts “waited #{Time.now - t} seconds” end locked at Thu May 28 01:03:23 +0100 2009 busy at Thu May 28 01:03:36 +0100 2009 posted at Thu May 28 01:04:13 +0100 2009 waited 47.056508 seconds Friday 12 November 2010
  • complexity • file locking • shared memory • message queues • transactional data stores Friday 12 November 2010
  • threads the popular approach to concurrency Friday 12 November 2010
  • threads under the hood from http://www.igvita.com/2008/11/13/concurrency-is-a-myth-in-ruby/ @igrigorik Friday 12 November 2010
  • the global lock • compatibility for 1.8 C extensions • only one thread executes at a time • scheduled fairly with a timer thread • 10 μs for Linux • 10 ms for Windows Friday 12 November 2010
  • synchronisation • thread groups • locks address race conditions • mutex + monitor • condition variable • deadlocks • livelocks Friday 12 November 2010
  • threads + sockets require 'socket' def send message require 'thread' self[:Q].enq message end class ThreadGroup def all_but_me &block def bind_transmitter socket list. delete_if { |t| raise ArgumentError if key? :XMIT t.name == Thread.current.name self[:socket] = socket }.each { |t| yield t } self[:XMIT] = Thread.new(socket, self[:Q]) do |s, q| end loop do case message = q.deq def select *names, &block when :EXIT: Thread.current.exit list.delete_if { |t| else s.puts message !names.include? t.name end }.each { |t| yield t } Thread.pass end end end end end class Thread def disconnect def name= n send "Goodbye #{name}" raise ArgumentError if key? :name send :EXIT self[:name] = n self[:XMIT].join self[:Q] = Queue.new self[:socket].close end exit end end Friday 12 November 2010
  • threads + sockets class ChatServer < TCPServer def listen socket def initialize port t = Thread.current @receivers = ThreadGroup.new loop do @transmitters = ThreadGroup.new message = socket.readline.chomp @deceased = ThreadGroup.new case message.downcase super when "bye" raise EOFError end when "known" list_known_users when "quit" raise SystemExit def register name, socket else broadcast "#{t.name}: #{message}" @receivers.add(t = Thread.current) end t.name = name end @transmitters.add t.bind_transmitter(socket) end broadcast "#{name} has joined the conversation" t.send "Welcome #{name}" def run end while socket = accept Thread.new(socket) do |socket| def connect socket begin loop do connect socket begin listen socket socket.print "Please enter your name: " rescue SystemExit register socket.readline.chomp).downcase, socket broadcast "#{Thread.current.name} terminated this conversation" break broadcast :EXIT rescue ArgumentError send :EXIT socket.puts "That name is already in use" @receivers.all_but_me { |t| t.transmitter.join } end Kernel.exit 0 end rescue EOFError end disconnect end def send message, name = Thread.current.name end @receivers.select(name) { |t| t.send message } end end end end def broadcast message @receivers.all_but_me { |t| t.send message } ChatServer.new(3939).run end Friday 12 November 2010
  • the macruby twist • grand central dispatch • uses an optimal number of threads • state is shared but not mutable • object-level queues for atomic mutability Friday 12 November 2010
  • clojure lisp dialect for the JVM refs software transactional memory agents independent, asynchronous change vars in-thread mutability check out Tim Bray’s Concur.next series Friday 12 November 2010
  • parallel banking (ns account)   ; ref   (def transactional-balance (ref 0))   ; transfer: within a transaction   (defn parallel-transfer [amount]      (dosync         (alter transactional-balance transfer amount)))   ; many threads adding 10 onto account   (defn parallel-stm [amount nthreads]      (let [threads (for [x (range 0 nthreads)] (Thread. #(parallel-transfer amount)))]         (do            (doall (map #(.start %) threads))            (doall (map #(.join %) threads))))      @transactional-balance) Friday 12 November 2010
  • ruby can do that too require 'clojure' include Clojure def parallel_transfer(amount)   Ref.dosync do     @balance.alter {|b| b + amount }   end end def parallel_stm(amount, nthreads)   threads = []   10.times do     threads << Thread.new do       parallel_transfer(amount)     end   end   threads.each {|t| t.join }   @balance.deref end @balance = Ref.new(0) puts parallel_stm(10,10) Friday 12 November 2010
  • enumerables everyday ruby code which is naturally concurrent Friday 12 November 2010
  • vector processing • collections are first-class values • single instruction multiple data • each datum is processed independently • successive instructions can be pipelined • so long as there are no side-effects Friday 12 November 2010
  • map/reduce • decompose into independent elements • process each element separately • use functional code without side-effects • recombine the elements • intrinsically suited to parallel execution Friday 12 November 2010
  • a two-phase operation concurrent sequential (0..5). x=0 to_a. (0..5). each { |i| puts i } to_a. each { |i| x = x + i } (0..5). (0..5). to_a. to_a. map { |i| i ** 2 } inject { |sum, i| sum + i } Friday 12 November 2010
  • parallel require 'brute_force' require 'parallel' # can be run with :in_processes as well mapped = Parallel.map((0..3).to_a, :in_threads => 4) do |num|   map("english.#{num}") # hash the whole dictionary end hashed = "71aa27d3bf313edf99f4302a65e4c042" puts reduce(hashed, mapped) # returns “zoned” Friday 12 November 2010
  • algebra, actors + events synchronising concurrency via communication Friday 12 November 2010
  • process calculi • mathematical model of interaction • processes and events • (a)synchronous message passing • named channels with atomic semantics Friday 12 November 2010
  • go • statically-typed compiled systems language • class-free object-orientation • garbage collection • independent lightweight coroutines • implicit cross-thread scheduling • channels are a first-class datatype Friday 12 November 2010
  • a signal generator package main func main() { import "syscall" c := Clock{1000, make(chan int64), make(chan bool), false} c.Start() func (c *Clock) Start() { if !c.active { for i := 0; i < 3; i++ { go func() { println("pulse value", <-c.Count, "from clock") c.active = true } for i := int64(0); ; i++ { select { println("disabling clock") case status := <- c.Control: c.Control <- false c.active = status syscall.Sleep(1000000) default: println("restarting clock") if c.active { c.Control <- true c.Count <- i println("pulse value", <-c.Count, "from clock") } } syscall.Sleep(c.Period) } } produces: }() pulse value 0 from clock } pulse value 1 from clock } pulse value 2 from clock disabling clock type Clock struct { restarting clock Period int64 pulse value 106 from clock Count chan int64 Control chan bool active bool } Friday 12 November 2010
  • homework • write a signal generator in ruby • hints: • it’s very easy • look at the thread + socket example • use atomic queues • and yes, ruby grok’s csp Friday 12 November 2010
  • actor model • named actors • fully independent of each other • asynchronous message passing • and no shared state Friday 12 November 2010
  • erlang • implements actors with green processes • efficient SMP-enabled VM • functional language • hot code loading • fault-tolerant © ericsson 2007 Friday 12 November 2010
  • erlang -module(brute_force). -import(plists). -export(run/2). map(FileName) ->     {ok, Binary} = file:read_file(FileName),     Lines = string:tokens(erlang:binary_to_list (Binary), "n"),     lists:map(fun(I) -> {erlang:md5(I), I} end, Lines). reduce(Hashed, Dictionary) ->     dict:fetch(Hashed, Dictionary). run(Hashed, Files) ->     Mapped = plists:map(fun(I) -> map(I) end, Files),     Values = lists:flatten(Mapped),     Dict = dict:from_list(Values),     reduce(Hashed, Dict). Friday 12 November 2010
  • erlang pmap(F, L) ->     S = self(),     Pids = lists:map(fun(I) -> spawn(fun() -> pmap_f(S, F, I) end) end, L),     pmap_gather(Pids). pmap_gather([H|T]) ->     receive         {H, Ret} -> [Ret|pmap_gather(T)]     end; pmap_gather([]) ->     []. pmap_f(Parent, F, I) ->     Parent ! {self(), (catch F(I))}. Friday 12 November 2010
  • rubinius actors • implemented as ruby threads • each thread has an inbox • cross-VM communication Friday 12 November 2010
  • rubinius actors require 'quick_sort' require 'actor' class RbxActorSort " def execute(&block) " " current = Actor.current " " Actor.spawn(current) {|current| current.send (block.call) } " " Actor.receive # could have filter " end end puts q = QuickSort.new([1,7,3,2,77,23,4,2,90,100,33,2,4], RbxActorSort).sort Friday 12 November 2010
  • ruby revactor • erlang-like semantics • actor spawn/receive • filter • uses fibers for cooperative scheduling • so only works on ruby 1.9 • non-blocking network access in ruby 1.9.2 Friday 12 November 2010
  • promises + futures • abstraction for delayed execution • a promise is calculated in parallel • a future is calculated on demand • caller will block when requesting result • until that result is ready Friday 12 November 2010
  • ruby futures Lazy.rb gem (@mentalguy) require 'lazy' require 'lazy/futures' def fib(n)   return n if (0..1).include? n   fib(n-1) + fib(n-2) if n > 1 end puts "before first future" future1 = Lazy::Future.new { fib(40) } puts "before second future" future2 = Lazy::Future.new { fib(40) } puts "and now we're waiting for results ... getting futures fulfilled is blocking" puts future1 puts future2 Friday 12 November 2010
  • we didn’t cover • tuple spaces (Rinda) • event-driven IO • petri nets • ... Friday 12 November 2010
  • reinventing? some of these problems have been solved before ... Friday 12 November 2010
  • to surmise • beware of shared mutable state • but: sane ways to handle concurrency • they are all possible in Ruby Friday 12 November 2010
  • fun! Friday 12 November 2010
  • further reading http://www.delicious.com/elisehuard/concurrency http://www.ecst.csuchico.edu/~beej/guide/ipc/ http://wiki.netbsd.se/kqueue_tutorial http://www.kegel.com/c10k.html Elise Huard @elise_huard http://jabberwocky.eu Eleanor McHugh @feyeleanor http://slides.games-with-brains.net Friday 12 November 2010